diff options
author | Adnan Begovic <adnan@cyngn.com> | 2015-10-06 16:26:05 -0700 |
---|---|---|
committer | Adnan Begovic <adnan@cyngn.com> | 2015-10-06 16:26:05 -0700 |
commit | d2fbc25a6761fa15e72bc25c114c4e5f91adeb24 (patch) | |
tree | c59713c6a77854f1209383cb9a90eb4e585dad37 | |
parent | d8b510ad324b2af51737a101e1989f0a7000b80f (diff) | |
download | build-d2fbc25a6761fa15e72bc25c114c4e5f91adeb24.zip build-d2fbc25a6761fa15e72bc25c114c4e5f91adeb24.tar.gz build-d2fbc25a6761fa15e72bc25c114c4e5f91adeb24.tar.bz2 |
Add temporary hack to help with merge resolution.
Change-Id: I1207daf17c2bd3f7f18e35a7705635752535942f
-rwxr-xr-x[l---------] | tools/releasetools/check_target_files_signatures | 443 | ||||
-rwxr-xr-x | tools/releasetools/check_target_files_signatures.py | 442 | ||||
l--------- | tools/releasetools/check_target_files_signatures.tmp | 1 | ||||
-rwxr-xr-x[l---------] | tools/releasetools/make_recovery_patch | 54 | ||||
-rwxr-xr-x | tools/releasetools/make_recovery_patch.py | 53 | ||||
l--------- | tools/releasetools/make_recovery_patch.tmp | 1 | ||||
-rwxr-xr-x[l---------] | tools/releasetools/sign_target_files_apks | 507 | ||||
-rwxr-xr-x | tools/releasetools/sign_target_files_apks.py | 506 | ||||
l--------- | tools/releasetools/sign_target_files_apks.tmp | 1 |
9 files changed, 1004 insertions, 1004 deletions
diff --git a/tools/releasetools/check_target_files_signatures b/tools/releasetools/check_target_files_signatures index 9f62aa3..5c541ab 120000..100755 --- a/tools/releasetools/check_target_files_signatures +++ b/tools/releasetools/check_target_files_signatures @@ -1 +1,442 @@ -check_target_files_signatures.py
\ No newline at end of file +#!/usr/bin/env python +# +# Copyright (C) 2009 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Check the signatures of all APKs in a target_files .zip file. With +-c, compare the signatures of each package to the ones in a separate +target_files (usually a previously distributed build for the same +device) and flag any changes. + +Usage: check_target_file_signatures [flags] target_files + + -c (--compare_with) <other_target_files> + Look for compatibility problems between the two sets of target + files (eg., packages whose keys have changed). + + -l (--local_cert_dirs) <dir,dir,...> + Comma-separated list of top-level directories to scan for + .x509.pem files. Defaults to "vendor,build". Where cert files + can be found that match APK signatures, the filename will be + printed as the cert name, otherwise a hash of the cert plus its + subject string will be printed instead. + + -t (--text) + Dump the certificate information for both packages in comparison + mode (this output is normally suppressed). + +""" + +import sys + +if sys.hexversion < 0x02070000: + print >> sys.stderr, "Python 2.7 or newer is required." + sys.exit(1) + +import os +import re +import shutil +import subprocess +import zipfile + +import common + +# Work around a bug in python's zipfile module that prevents opening +# of zipfiles if any entry has an extra field of between 1 and 3 bytes +# (which is common with zipaligned APKs). This overrides the +# ZipInfo._decodeExtra() method (which contains the bug) with an empty +# version (since we don't need to decode the extra field anyway). +class MyZipInfo(zipfile.ZipInfo): + def _decodeExtra(self): + pass +zipfile.ZipInfo = MyZipInfo + +OPTIONS = common.OPTIONS + +OPTIONS.text = False +OPTIONS.compare_with = None +OPTIONS.local_cert_dirs = ("vendor", "build") + +PROBLEMS = [] +PROBLEM_PREFIX = [] + +def AddProblem(msg): + PROBLEMS.append(" ".join(PROBLEM_PREFIX) + " " + msg) +def Push(msg): + PROBLEM_PREFIX.append(msg) +def Pop(): + PROBLEM_PREFIX.pop() + + +def Banner(msg): + print "-" * 70 + print " ", msg + print "-" * 70 + + +def GetCertSubject(cert): + p = common.Run(["openssl", "x509", "-inform", "DER", "-text"], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + out, err = p.communicate(cert) + if err and not err.strip(): + return "(error reading cert subject)" + for line in out.split("\n"): + line = line.strip() + if line.startswith("Subject:"): + return line[8:].strip() + return "(unknown cert subject)" + + +class CertDB(object): + def __init__(self): + self.certs = {} + + def Add(self, cert, name=None): + if cert in self.certs: + if name: + self.certs[cert] = self.certs[cert] + "," + name + else: + if name is None: + name = "unknown cert %s (%s)" % (common.sha1(cert).hexdigest()[:12], + GetCertSubject(cert)) + self.certs[cert] = name + + def Get(self, cert): + """Return the name for a given cert.""" + return self.certs.get(cert, None) + + def FindLocalCerts(self): + to_load = [] + for top in OPTIONS.local_cert_dirs: + for dirpath, _, filenames in os.walk(top): + certs = [os.path.join(dirpath, i) + for i in filenames if i.endswith(".x509.pem")] + if certs: + to_load.extend(certs) + + for i in to_load: + f = open(i) + cert = common.ParseCertificate(f.read()) + f.close() + name, _ = os.path.splitext(i) + name, _ = os.path.splitext(name) + self.Add(cert, name) + +ALL_CERTS = CertDB() + + +def CertFromPKCS7(data, filename): + """Read the cert out of a PKCS#7-format file (which is what is + stored in a signed .apk).""" + Push(filename + ":") + try: + p = common.Run(["openssl", "pkcs7", + "-inform", "DER", + "-outform", "PEM", + "-print_certs"], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + out, err = p.communicate(data) + if err and not err.strip(): + AddProblem("error reading cert:\n" + err) + return None + + cert = common.ParseCertificate(out) + if not cert: + AddProblem("error parsing cert output") + return None + return cert + finally: + Pop() + + +class APK(object): + def __init__(self, full_filename, filename): + self.filename = filename + self.certs = None + self.shared_uid = None + self.package = None + + Push(filename+":") + try: + self.RecordCerts(full_filename) + self.ReadManifest(full_filename) + finally: + Pop() + + def RecordCerts(self, full_filename): + out = set() + try: + f = open(full_filename) + apk = zipfile.ZipFile(f, "r") + pkcs7 = None + for info in apk.infolist(): + if info.filename.startswith("META-INF/") and \ + (info.filename.endswith(".DSA") or info.filename.endswith(".RSA")): + pkcs7 = apk.read(info.filename) + cert = CertFromPKCS7(pkcs7, info.filename) + out.add(cert) + ALL_CERTS.Add(cert) + if not pkcs7: + AddProblem("no signature") + finally: + f.close() + self.certs = frozenset(out) + + def ReadManifest(self, full_filename): + p = common.Run(["aapt", "dump", "xmltree", full_filename, + "AndroidManifest.xml"], + stdout=subprocess.PIPE) + manifest, err = p.communicate() + if err: + AddProblem("failed to read manifest") + return + + self.shared_uid = None + self.package = None + + for line in manifest.split("\n"): + line = line.strip() + m = re.search(r'A: (\S*?)(?:\(0x[0-9a-f]+\))?="(.*?)" \(Raw', line) + if m: + name = m.group(1) + if name == "android:sharedUserId": + if self.shared_uid is not None: + AddProblem("multiple sharedUserId declarations") + self.shared_uid = m.group(2) + elif name == "package": + if self.package is not None: + AddProblem("multiple package declarations") + self.package = m.group(2) + + if self.package is None: + AddProblem("no package declaration") + + +class TargetFiles(object): + def __init__(self): + self.max_pkg_len = 30 + self.max_fn_len = 20 + self.apks = None + self.apks_by_basename = None + self.certmap = None + + def LoadZipFile(self, filename): + d, z = common.UnzipTemp(filename, '*.apk') + try: + self.apks = {} + self.apks_by_basename = {} + for dirpath, _, filenames in os.walk(d): + for fn in filenames: + if fn.endswith(".apk"): + fullname = os.path.join(dirpath, fn) + displayname = fullname[len(d)+1:] + apk = APK(fullname, displayname) + self.apks[apk.package] = apk + self.apks_by_basename[os.path.basename(apk.filename)] = apk + + self.max_pkg_len = max(self.max_pkg_len, len(apk.package)) + self.max_fn_len = max(self.max_fn_len, len(apk.filename)) + finally: + shutil.rmtree(d) + + self.certmap = common.ReadApkCerts(z) + z.close() + + def CheckSharedUids(self): + """Look for any instances where packages signed with different + certs request the same sharedUserId.""" + apks_by_uid = {} + for apk in self.apks.itervalues(): + if apk.shared_uid: + apks_by_uid.setdefault(apk.shared_uid, []).append(apk) + + for uid in sorted(apks_by_uid.keys()): + apks = apks_by_uid[uid] + for apk in apks[1:]: + if apk.certs != apks[0].certs: + break + else: + # all packages have the same set of certs; this uid is fine. + continue + + AddProblem("different cert sets for packages with uid %s" % (uid,)) + + print "uid %s is shared by packages with different cert sets:" % (uid,) + for apk in apks: + print "%-*s [%s]" % (self.max_pkg_len, apk.package, apk.filename) + for cert in apk.certs: + print " ", ALL_CERTS.Get(cert) + print + + def CheckExternalSignatures(self): + for apk_filename, certname in self.certmap.iteritems(): + if certname == "EXTERNAL": + # Apps marked EXTERNAL should be signed with the test key + # during development, then manually re-signed after + # predexopting. Consider it an error if this app is now + # signed with any key that is present in our tree. + apk = self.apks_by_basename[apk_filename] + name = ALL_CERTS.Get(apk.cert) + if not name.startswith("unknown "): + Push(apk.filename) + AddProblem("hasn't been signed with EXTERNAL cert") + Pop() + + def PrintCerts(self): + """Display a table of packages grouped by cert.""" + by_cert = {} + for apk in self.apks.itervalues(): + for cert in apk.certs: + by_cert.setdefault(cert, []).append((apk.package, apk)) + + order = [(-len(v), k) for (k, v) in by_cert.iteritems()] + order.sort() + + for _, cert in order: + print "%s:" % (ALL_CERTS.Get(cert),) + apks = by_cert[cert] + apks.sort() + for _, apk in apks: + if apk.shared_uid: + print " %-*s %-*s [%s]" % (self.max_fn_len, apk.filename, + self.max_pkg_len, apk.package, + apk.shared_uid) + else: + print " %-*s %-*s" % (self.max_fn_len, apk.filename, + self.max_pkg_len, apk.package) + print + + def CompareWith(self, other): + """Look for instances where a given package that exists in both + self and other have different certs.""" + + all_apks = set(self.apks.keys()) + all_apks.update(other.apks.keys()) + + max_pkg_len = max(self.max_pkg_len, other.max_pkg_len) + + by_certpair = {} + + for i in all_apks: + if i in self.apks: + if i in other.apks: + # in both; should have same set of certs + if self.apks[i].certs != other.apks[i].certs: + by_certpair.setdefault((other.apks[i].certs, + self.apks[i].certs), []).append(i) + else: + print "%s [%s]: new APK (not in comparison target_files)" % ( + i, self.apks[i].filename) + else: + if i in other.apks: + print "%s [%s]: removed APK (only in comparison target_files)" % ( + i, other.apks[i].filename) + + if by_certpair: + AddProblem("some APKs changed certs") + Banner("APK signing differences") + for (old, new), packages in sorted(by_certpair.items()): + for i, o in enumerate(old): + if i == 0: + print "was", ALL_CERTS.Get(o) + else: + print " ", ALL_CERTS.Get(o) + for i, n in enumerate(new): + if i == 0: + print "now", ALL_CERTS.Get(n) + else: + print " ", ALL_CERTS.Get(n) + for i in sorted(packages): + old_fn = other.apks[i].filename + new_fn = self.apks[i].filename + if old_fn == new_fn: + print " %-*s [%s]" % (max_pkg_len, i, old_fn) + else: + print " %-*s [was: %s; now: %s]" % (max_pkg_len, i, + old_fn, new_fn) + print + + +def main(argv): + def option_handler(o, a): + if o in ("-c", "--compare_with"): + OPTIONS.compare_with = a + elif o in ("-l", "--local_cert_dirs"): + OPTIONS.local_cert_dirs = [i.strip() for i in a.split(",")] + elif o in ("-t", "--text"): + OPTIONS.text = True + else: + return False + return True + + args = common.ParseOptions(argv, __doc__, + extra_opts="c:l:t", + extra_long_opts=["compare_with=", + "local_cert_dirs="], + extra_option_handler=option_handler) + + if len(args) != 1: + common.Usage(__doc__) + sys.exit(1) + + ALL_CERTS.FindLocalCerts() + + Push("input target_files:") + try: + target_files = TargetFiles() + target_files.LoadZipFile(args[0]) + finally: + Pop() + + compare_files = None + if OPTIONS.compare_with: + Push("comparison target_files:") + try: + compare_files = TargetFiles() + compare_files.LoadZipFile(OPTIONS.compare_with) + finally: + Pop() + + if OPTIONS.text or not compare_files: + Banner("target files") + target_files.PrintCerts() + target_files.CheckSharedUids() + target_files.CheckExternalSignatures() + if compare_files: + if OPTIONS.text: + Banner("comparison files") + compare_files.PrintCerts() + target_files.CompareWith(compare_files) + + if PROBLEMS: + print "%d problem(s) found:\n" % (len(PROBLEMS),) + for p in PROBLEMS: + print p + return 1 + + return 0 + + +if __name__ == '__main__': + try: + r = main(sys.argv[1:]) + sys.exit(r) + except common.ExternalError as e: + print + print " ERROR: %s" % (e,) + print + sys.exit(1) diff --git a/tools/releasetools/check_target_files_signatures.py b/tools/releasetools/check_target_files_signatures.py deleted file mode 100755 index 5c541ab..0000000 --- a/tools/releasetools/check_target_files_signatures.py +++ /dev/null @@ -1,442 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (C) 2009 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Check the signatures of all APKs in a target_files .zip file. With --c, compare the signatures of each package to the ones in a separate -target_files (usually a previously distributed build for the same -device) and flag any changes. - -Usage: check_target_file_signatures [flags] target_files - - -c (--compare_with) <other_target_files> - Look for compatibility problems between the two sets of target - files (eg., packages whose keys have changed). - - -l (--local_cert_dirs) <dir,dir,...> - Comma-separated list of top-level directories to scan for - .x509.pem files. Defaults to "vendor,build". Where cert files - can be found that match APK signatures, the filename will be - printed as the cert name, otherwise a hash of the cert plus its - subject string will be printed instead. - - -t (--text) - Dump the certificate information for both packages in comparison - mode (this output is normally suppressed). - -""" - -import sys - -if sys.hexversion < 0x02070000: - print >> sys.stderr, "Python 2.7 or newer is required." - sys.exit(1) - -import os -import re -import shutil -import subprocess -import zipfile - -import common - -# Work around a bug in python's zipfile module that prevents opening -# of zipfiles if any entry has an extra field of between 1 and 3 bytes -# (which is common with zipaligned APKs). This overrides the -# ZipInfo._decodeExtra() method (which contains the bug) with an empty -# version (since we don't need to decode the extra field anyway). -class MyZipInfo(zipfile.ZipInfo): - def _decodeExtra(self): - pass -zipfile.ZipInfo = MyZipInfo - -OPTIONS = common.OPTIONS - -OPTIONS.text = False -OPTIONS.compare_with = None -OPTIONS.local_cert_dirs = ("vendor", "build") - -PROBLEMS = [] -PROBLEM_PREFIX = [] - -def AddProblem(msg): - PROBLEMS.append(" ".join(PROBLEM_PREFIX) + " " + msg) -def Push(msg): - PROBLEM_PREFIX.append(msg) -def Pop(): - PROBLEM_PREFIX.pop() - - -def Banner(msg): - print "-" * 70 - print " ", msg - print "-" * 70 - - -def GetCertSubject(cert): - p = common.Run(["openssl", "x509", "-inform", "DER", "-text"], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE) - out, err = p.communicate(cert) - if err and not err.strip(): - return "(error reading cert subject)" - for line in out.split("\n"): - line = line.strip() - if line.startswith("Subject:"): - return line[8:].strip() - return "(unknown cert subject)" - - -class CertDB(object): - def __init__(self): - self.certs = {} - - def Add(self, cert, name=None): - if cert in self.certs: - if name: - self.certs[cert] = self.certs[cert] + "," + name - else: - if name is None: - name = "unknown cert %s (%s)" % (common.sha1(cert).hexdigest()[:12], - GetCertSubject(cert)) - self.certs[cert] = name - - def Get(self, cert): - """Return the name for a given cert.""" - return self.certs.get(cert, None) - - def FindLocalCerts(self): - to_load = [] - for top in OPTIONS.local_cert_dirs: - for dirpath, _, filenames in os.walk(top): - certs = [os.path.join(dirpath, i) - for i in filenames if i.endswith(".x509.pem")] - if certs: - to_load.extend(certs) - - for i in to_load: - f = open(i) - cert = common.ParseCertificate(f.read()) - f.close() - name, _ = os.path.splitext(i) - name, _ = os.path.splitext(name) - self.Add(cert, name) - -ALL_CERTS = CertDB() - - -def CertFromPKCS7(data, filename): - """Read the cert out of a PKCS#7-format file (which is what is - stored in a signed .apk).""" - Push(filename + ":") - try: - p = common.Run(["openssl", "pkcs7", - "-inform", "DER", - "-outform", "PEM", - "-print_certs"], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE) - out, err = p.communicate(data) - if err and not err.strip(): - AddProblem("error reading cert:\n" + err) - return None - - cert = common.ParseCertificate(out) - if not cert: - AddProblem("error parsing cert output") - return None - return cert - finally: - Pop() - - -class APK(object): - def __init__(self, full_filename, filename): - self.filename = filename - self.certs = None - self.shared_uid = None - self.package = None - - Push(filename+":") - try: - self.RecordCerts(full_filename) - self.ReadManifest(full_filename) - finally: - Pop() - - def RecordCerts(self, full_filename): - out = set() - try: - f = open(full_filename) - apk = zipfile.ZipFile(f, "r") - pkcs7 = None - for info in apk.infolist(): - if info.filename.startswith("META-INF/") and \ - (info.filename.endswith(".DSA") or info.filename.endswith(".RSA")): - pkcs7 = apk.read(info.filename) - cert = CertFromPKCS7(pkcs7, info.filename) - out.add(cert) - ALL_CERTS.Add(cert) - if not pkcs7: - AddProblem("no signature") - finally: - f.close() - self.certs = frozenset(out) - - def ReadManifest(self, full_filename): - p = common.Run(["aapt", "dump", "xmltree", full_filename, - "AndroidManifest.xml"], - stdout=subprocess.PIPE) - manifest, err = p.communicate() - if err: - AddProblem("failed to read manifest") - return - - self.shared_uid = None - self.package = None - - for line in manifest.split("\n"): - line = line.strip() - m = re.search(r'A: (\S*?)(?:\(0x[0-9a-f]+\))?="(.*?)" \(Raw', line) - if m: - name = m.group(1) - if name == "android:sharedUserId": - if self.shared_uid is not None: - AddProblem("multiple sharedUserId declarations") - self.shared_uid = m.group(2) - elif name == "package": - if self.package is not None: - AddProblem("multiple package declarations") - self.package = m.group(2) - - if self.package is None: - AddProblem("no package declaration") - - -class TargetFiles(object): - def __init__(self): - self.max_pkg_len = 30 - self.max_fn_len = 20 - self.apks = None - self.apks_by_basename = None - self.certmap = None - - def LoadZipFile(self, filename): - d, z = common.UnzipTemp(filename, '*.apk') - try: - self.apks = {} - self.apks_by_basename = {} - for dirpath, _, filenames in os.walk(d): - for fn in filenames: - if fn.endswith(".apk"): - fullname = os.path.join(dirpath, fn) - displayname = fullname[len(d)+1:] - apk = APK(fullname, displayname) - self.apks[apk.package] = apk - self.apks_by_basename[os.path.basename(apk.filename)] = apk - - self.max_pkg_len = max(self.max_pkg_len, len(apk.package)) - self.max_fn_len = max(self.max_fn_len, len(apk.filename)) - finally: - shutil.rmtree(d) - - self.certmap = common.ReadApkCerts(z) - z.close() - - def CheckSharedUids(self): - """Look for any instances where packages signed with different - certs request the same sharedUserId.""" - apks_by_uid = {} - for apk in self.apks.itervalues(): - if apk.shared_uid: - apks_by_uid.setdefault(apk.shared_uid, []).append(apk) - - for uid in sorted(apks_by_uid.keys()): - apks = apks_by_uid[uid] - for apk in apks[1:]: - if apk.certs != apks[0].certs: - break - else: - # all packages have the same set of certs; this uid is fine. - continue - - AddProblem("different cert sets for packages with uid %s" % (uid,)) - - print "uid %s is shared by packages with different cert sets:" % (uid,) - for apk in apks: - print "%-*s [%s]" % (self.max_pkg_len, apk.package, apk.filename) - for cert in apk.certs: - print " ", ALL_CERTS.Get(cert) - print - - def CheckExternalSignatures(self): - for apk_filename, certname in self.certmap.iteritems(): - if certname == "EXTERNAL": - # Apps marked EXTERNAL should be signed with the test key - # during development, then manually re-signed after - # predexopting. Consider it an error if this app is now - # signed with any key that is present in our tree. - apk = self.apks_by_basename[apk_filename] - name = ALL_CERTS.Get(apk.cert) - if not name.startswith("unknown "): - Push(apk.filename) - AddProblem("hasn't been signed with EXTERNAL cert") - Pop() - - def PrintCerts(self): - """Display a table of packages grouped by cert.""" - by_cert = {} - for apk in self.apks.itervalues(): - for cert in apk.certs: - by_cert.setdefault(cert, []).append((apk.package, apk)) - - order = [(-len(v), k) for (k, v) in by_cert.iteritems()] - order.sort() - - for _, cert in order: - print "%s:" % (ALL_CERTS.Get(cert),) - apks = by_cert[cert] - apks.sort() - for _, apk in apks: - if apk.shared_uid: - print " %-*s %-*s [%s]" % (self.max_fn_len, apk.filename, - self.max_pkg_len, apk.package, - apk.shared_uid) - else: - print " %-*s %-*s" % (self.max_fn_len, apk.filename, - self.max_pkg_len, apk.package) - print - - def CompareWith(self, other): - """Look for instances where a given package that exists in both - self and other have different certs.""" - - all_apks = set(self.apks.keys()) - all_apks.update(other.apks.keys()) - - max_pkg_len = max(self.max_pkg_len, other.max_pkg_len) - - by_certpair = {} - - for i in all_apks: - if i in self.apks: - if i in other.apks: - # in both; should have same set of certs - if self.apks[i].certs != other.apks[i].certs: - by_certpair.setdefault((other.apks[i].certs, - self.apks[i].certs), []).append(i) - else: - print "%s [%s]: new APK (not in comparison target_files)" % ( - i, self.apks[i].filename) - else: - if i in other.apks: - print "%s [%s]: removed APK (only in comparison target_files)" % ( - i, other.apks[i].filename) - - if by_certpair: - AddProblem("some APKs changed certs") - Banner("APK signing differences") - for (old, new), packages in sorted(by_certpair.items()): - for i, o in enumerate(old): - if i == 0: - print "was", ALL_CERTS.Get(o) - else: - print " ", ALL_CERTS.Get(o) - for i, n in enumerate(new): - if i == 0: - print "now", ALL_CERTS.Get(n) - else: - print " ", ALL_CERTS.Get(n) - for i in sorted(packages): - old_fn = other.apks[i].filename - new_fn = self.apks[i].filename - if old_fn == new_fn: - print " %-*s [%s]" % (max_pkg_len, i, old_fn) - else: - print " %-*s [was: %s; now: %s]" % (max_pkg_len, i, - old_fn, new_fn) - print - - -def main(argv): - def option_handler(o, a): - if o in ("-c", "--compare_with"): - OPTIONS.compare_with = a - elif o in ("-l", "--local_cert_dirs"): - OPTIONS.local_cert_dirs = [i.strip() for i in a.split(",")] - elif o in ("-t", "--text"): - OPTIONS.text = True - else: - return False - return True - - args = common.ParseOptions(argv, __doc__, - extra_opts="c:l:t", - extra_long_opts=["compare_with=", - "local_cert_dirs="], - extra_option_handler=option_handler) - - if len(args) != 1: - common.Usage(__doc__) - sys.exit(1) - - ALL_CERTS.FindLocalCerts() - - Push("input target_files:") - try: - target_files = TargetFiles() - target_files.LoadZipFile(args[0]) - finally: - Pop() - - compare_files = None - if OPTIONS.compare_with: - Push("comparison target_files:") - try: - compare_files = TargetFiles() - compare_files.LoadZipFile(OPTIONS.compare_with) - finally: - Pop() - - if OPTIONS.text or not compare_files: - Banner("target files") - target_files.PrintCerts() - target_files.CheckSharedUids() - target_files.CheckExternalSignatures() - if compare_files: - if OPTIONS.text: - Banner("comparison files") - compare_files.PrintCerts() - target_files.CompareWith(compare_files) - - if PROBLEMS: - print "%d problem(s) found:\n" % (len(PROBLEMS),) - for p in PROBLEMS: - print p - return 1 - - return 0 - - -if __name__ == '__main__': - try: - r = main(sys.argv[1:]) - sys.exit(r) - except common.ExternalError as e: - print - print " ERROR: %s" % (e,) - print - sys.exit(1) diff --git a/tools/releasetools/check_target_files_signatures.tmp b/tools/releasetools/check_target_files_signatures.tmp new file mode 120000 index 0000000..9f62aa3 --- /dev/null +++ b/tools/releasetools/check_target_files_signatures.tmp @@ -0,0 +1 @@ +check_target_files_signatures.py
\ No newline at end of file diff --git a/tools/releasetools/make_recovery_patch b/tools/releasetools/make_recovery_patch index 45cec08..08d1450 120000..100755 --- a/tools/releasetools/make_recovery_patch +++ b/tools/releasetools/make_recovery_patch @@ -1 +1,53 @@ -make_recovery_patch.py
\ No newline at end of file +#!/usr/bin/env python +# +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +if sys.hexversion < 0x02070000: + print >> sys.stderr, "Python 2.7 or newer is required." + sys.exit(1) + +import os +import common + +OPTIONS = common.OPTIONS + +def main(argv): + # def option_handler(o, a): + # return False + + args = common.ParseOptions(argv, __doc__) + input_dir, output_dir = args + + OPTIONS.info_dict = common.LoadInfoDict(input_dir) + + recovery_img = common.GetBootableImage("recovery.img", "recovery.img", + input_dir, "RECOVERY") + boot_img = common.GetBootableImage("boot.img", "boot.img", + input_dir, "BOOT") + + if not recovery_img or not boot_img: + sys.exit(0) + + def output_sink(fn, data): + with open(os.path.join(output_dir, "SYSTEM", *fn.split("/")), "wb") as f: + f.write(data) + + common.MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/tools/releasetools/make_recovery_patch.py b/tools/releasetools/make_recovery_patch.py deleted file mode 100755 index 08d1450..0000000 --- a/tools/releasetools/make_recovery_patch.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (C) 2014 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import sys - -if sys.hexversion < 0x02070000: - print >> sys.stderr, "Python 2.7 or newer is required." - sys.exit(1) - -import os -import common - -OPTIONS = common.OPTIONS - -def main(argv): - # def option_handler(o, a): - # return False - - args = common.ParseOptions(argv, __doc__) - input_dir, output_dir = args - - OPTIONS.info_dict = common.LoadInfoDict(input_dir) - - recovery_img = common.GetBootableImage("recovery.img", "recovery.img", - input_dir, "RECOVERY") - boot_img = common.GetBootableImage("boot.img", "boot.img", - input_dir, "BOOT") - - if not recovery_img or not boot_img: - sys.exit(0) - - def output_sink(fn, data): - with open(os.path.join(output_dir, "SYSTEM", *fn.split("/")), "wb") as f: - f.write(data) - - common.MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img) - - -if __name__ == '__main__': - main(sys.argv[1:]) diff --git a/tools/releasetools/make_recovery_patch.tmp b/tools/releasetools/make_recovery_patch.tmp new file mode 120000 index 0000000..45cec08 --- /dev/null +++ b/tools/releasetools/make_recovery_patch.tmp @@ -0,0 +1 @@ +make_recovery_patch.py
\ No newline at end of file diff --git a/tools/releasetools/sign_target_files_apks b/tools/releasetools/sign_target_files_apks index b5ec59a..ec49112 120000..100755 --- a/tools/releasetools/sign_target_files_apks +++ b/tools/releasetools/sign_target_files_apks @@ -1 +1,506 @@ -sign_target_files_apks.py
\ No newline at end of file +#!/usr/bin/env python +# +# Copyright (C) 2008 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Signs all the APK files in a target-files zipfile, producing a new +target-files zip. + +Usage: sign_target_files_apks [flags] input_target_files output_target_files + + -e (--extra_apks) <name,name,...=key> + Add extra APK name/key pairs as though they appeared in + apkcerts.txt (so mappings specified by -k and -d are applied). + Keys specified in -e override any value for that app contained + in the apkcerts.txt file. Option may be repeated to give + multiple extra packages. + + -k (--key_mapping) <src_key=dest_key> + Add a mapping from the key name as specified in apkcerts.txt (the + src_key) to the real key you wish to sign the package with + (dest_key). Option may be repeated to give multiple key + mappings. + + -d (--default_key_mappings) <dir> + Set up the following key mappings: + + $devkey/devkey ==> $dir/releasekey + $devkey/testkey ==> $dir/releasekey + $devkey/media ==> $dir/media + $devkey/shared ==> $dir/shared + $devkey/platform ==> $dir/platform + + where $devkey is the directory part of the value of + default_system_dev_certificate from the input target-files's + META/misc_info.txt. (Defaulting to "build/target/product/security" + if the value is not present in misc_info. + + -d and -k options are added to the set of mappings in the order + in which they appear on the command line. + + -o (--replace_ota_keys) + Replace the certificate (public key) used by OTA package + verification with the one specified in the input target_files + zip (in the META/otakeys.txt file). Key remapping (-k and -d) + is performed on this key. + + -t (--tag_changes) <+tag>,<-tag>,... + Comma-separated list of changes to make to the set of tags (in + the last component of the build fingerprint). Prefix each with + '+' or '-' to indicate whether that tag should be added or + removed. Changes are processed in the order they appear. + Default value is "-test-keys,-dev-keys,+release-keys". + +""" + +import sys + +if sys.hexversion < 0x02070000: + print >> sys.stderr, "Python 2.7 or newer is required." + sys.exit(1) + +import base64 +import cStringIO +import copy +import errno +import os +import re +import shutil +import subprocess +import tempfile +import zipfile + +import add_img_to_target_files +import common + +OPTIONS = common.OPTIONS + +OPTIONS.extra_apks = {} +OPTIONS.key_map = {} +OPTIONS.replace_ota_keys = False +OPTIONS.replace_verity_public_key = False +OPTIONS.replace_verity_private_key = False +OPTIONS.tag_changes = ("-test-keys", "-dev-keys", "+release-keys") + +def GetApkCerts(tf_zip): + certmap = common.ReadApkCerts(tf_zip) + + # apply the key remapping to the contents of the file + for apk, cert in certmap.iteritems(): + certmap[apk] = OPTIONS.key_map.get(cert, cert) + + # apply all the -e options, overriding anything in the file + for apk, cert in OPTIONS.extra_apks.iteritems(): + if not cert: + cert = "PRESIGNED" + certmap[apk] = OPTIONS.key_map.get(cert, cert) + + return certmap + + +def CheckAllApksSigned(input_tf_zip, apk_key_map): + """Check that all the APKs we want to sign have keys specified, and + error out if they don't.""" + unknown_apks = [] + for info in input_tf_zip.infolist(): + if info.filename.endswith(".apk"): + name = os.path.basename(info.filename) + if name not in apk_key_map: + unknown_apks.append(name) + if unknown_apks: + print "ERROR: no key specified for:\n\n ", + print "\n ".join(unknown_apks) + print "\nUse '-e <apkname>=' to specify a key (which may be an" + print "empty string to not sign this apk)." + sys.exit(1) + + +def SignApk(data, keyname, pw): + unsigned = tempfile.NamedTemporaryFile() + unsigned.write(data) + unsigned.flush() + + signed = tempfile.NamedTemporaryFile() + + common.SignFile(unsigned.name, signed.name, keyname, pw, align=4) + + data = signed.read() + unsigned.close() + signed.close() + + return data + + +def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info, + apk_key_map, key_passwords): + + maxsize = max([len(os.path.basename(i.filename)) + for i in input_tf_zip.infolist() + if i.filename.endswith('.apk')]) + rebuild_recovery = False + + tmpdir = tempfile.mkdtemp() + def write_to_temp(fn, attr, data): + fn = os.path.join(tmpdir, fn) + if fn.endswith("/"): + fn = os.path.join(tmpdir, fn) + os.mkdir(fn) + else: + d = os.path.dirname(fn) + if d and not os.path.exists(d): + os.makedirs(d) + + if attr >> 16 == 0xa1ff: + os.symlink(data, fn) + else: + with open(fn, "wb") as f: + f.write(data) + + for info in input_tf_zip.infolist(): + if info.filename.startswith("IMAGES/"): + continue + + data = input_tf_zip.read(info.filename) + out_info = copy.copy(info) + + if (info.filename == "META/misc_info.txt" and + OPTIONS.replace_verity_private_key): + ReplaceVerityPrivateKey(input_tf_zip, output_tf_zip, misc_info, + OPTIONS.replace_verity_private_key[1]) + elif (info.filename == "BOOT/RAMDISK/verity_key" and + OPTIONS.replace_verity_public_key): + new_data = ReplaceVerityPublicKey(output_tf_zip, + OPTIONS.replace_verity_public_key[1]) + write_to_temp(info.filename, info.external_attr, new_data) + elif (info.filename.startswith("BOOT/") or + info.filename.startswith("RECOVERY/") or + info.filename.startswith("META/") or + info.filename == "SYSTEM/etc/recovery-resource.dat"): + write_to_temp(info.filename, info.external_attr, data) + + if info.filename.endswith(".apk"): + name = os.path.basename(info.filename) + key = apk_key_map[name] + if key not in common.SPECIAL_CERT_STRINGS: + print " signing: %-*s (%s)" % (maxsize, name, key) + signed_data = SignApk(data, key, key_passwords[key]) + common.ZipWriteStr(output_tf_zip, out_info, signed_data) + else: + # an APK we're not supposed to sign. + print "NOT signing: %s" % (name,) + common.ZipWriteStr(output_tf_zip, out_info, data) + elif info.filename in ("SYSTEM/build.prop", + "VENDOR/build.prop", + "RECOVERY/RAMDISK/default.prop"): + print "rewriting %s:" % (info.filename,) + new_data = RewriteProps(data, misc_info) + common.ZipWriteStr(output_tf_zip, out_info, new_data) + if info.filename == "RECOVERY/RAMDISK/default.prop": + write_to_temp(info.filename, info.external_attr, new_data) + elif info.filename.endswith("mac_permissions.xml"): + print "rewriting %s with new keys." % (info.filename,) + new_data = ReplaceCerts(data) + common.ZipWriteStr(output_tf_zip, out_info, new_data) + elif info.filename in ("SYSTEM/recovery-from-boot.p", + "SYSTEM/bin/install-recovery.sh"): + rebuild_recovery = True + elif (OPTIONS.replace_ota_keys and + info.filename in ("RECOVERY/RAMDISK/res/keys", + "SYSTEM/etc/security/otacerts.zip")): + # don't copy these files if we're regenerating them below + pass + elif (OPTIONS.replace_verity_private_key and + info.filename == "META/misc_info.txt"): + pass + elif (OPTIONS.replace_verity_public_key and + info.filename == "BOOT/RAMDISK/verity_key"): + pass + else: + # a non-APK file; copy it verbatim + common.ZipWriteStr(output_tf_zip, out_info, data) + + if OPTIONS.replace_ota_keys: + new_recovery_keys = ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info) + if new_recovery_keys: + write_to_temp("RECOVERY/RAMDISK/res/keys", 0o755 << 16, new_recovery_keys) + + if rebuild_recovery: + recovery_img = common.GetBootableImage( + "recovery.img", "recovery.img", tmpdir, "RECOVERY", info_dict=misc_info) + boot_img = common.GetBootableImage( + "boot.img", "boot.img", tmpdir, "BOOT", info_dict=misc_info) + + def output_sink(fn, data): + common.ZipWriteStr(output_tf_zip, "SYSTEM/" + fn, data) + + common.MakeRecoveryPatch(tmpdir, output_sink, recovery_img, boot_img, + info_dict=misc_info) + + shutil.rmtree(tmpdir) + + +def ReplaceCerts(data): + """Given a string of data, replace all occurences of a set + of X509 certs with a newer set of X509 certs and return + the updated data string.""" + for old, new in OPTIONS.key_map.iteritems(): + try: + if OPTIONS.verbose: + print " Replacing %s.x509.pem with %s.x509.pem" % (old, new) + f = open(old + ".x509.pem") + old_cert16 = base64.b16encode(common.ParseCertificate(f.read())).lower() + f.close() + f = open(new + ".x509.pem") + new_cert16 = base64.b16encode(common.ParseCertificate(f.read())).lower() + f.close() + # Only match entire certs. + pattern = "\\b"+old_cert16+"\\b" + (data, num) = re.subn(pattern, new_cert16, data, flags=re.IGNORECASE) + if OPTIONS.verbose: + print " Replaced %d occurence(s) of %s.x509.pem with " \ + "%s.x509.pem" % (num, old, new) + except IOError as e: + if e.errno == errno.ENOENT and not OPTIONS.verbose: + continue + + print " Error accessing %s. %s. Skip replacing %s.x509.pem " \ + "with %s.x509.pem." % (e.filename, e.strerror, old, new) + + return data + + +def EditTags(tags): + """Given a string containing comma-separated tags, apply the edits + specified in OPTIONS.tag_changes and return the updated string.""" + tags = set(tags.split(",")) + for ch in OPTIONS.tag_changes: + if ch[0] == "-": + tags.discard(ch[1:]) + elif ch[0] == "+": + tags.add(ch[1:]) + return ",".join(sorted(tags)) + + +def RewriteProps(data, misc_info): + output = [] + for line in data.split("\n"): + line = line.strip() + original_line = line + if line and line[0] != '#' and "=" in line: + key, value = line.split("=", 1) + if (key in ("ro.build.fingerprint", "ro.vendor.build.fingerprint") + and misc_info.get("oem_fingerprint_properties") is None): + pieces = value.split("/") + pieces[-1] = EditTags(pieces[-1]) + value = "/".join(pieces) + elif (key in ("ro.build.thumbprint", "ro.vendor.build.thumbprint") + and misc_info.get("oem_fingerprint_properties") is not None): + pieces = value.split("/") + pieces[-1] = EditTags(pieces[-1]) + value = "/".join(pieces) + elif key == "ro.build.description": + pieces = value.split(" ") + assert len(pieces) == 5 + pieces[-1] = EditTags(pieces[-1]) + value = " ".join(pieces) + elif key == "ro.build.tags": + value = EditTags(value) + elif key == "ro.build.display.id": + # change, eg, "JWR66N dev-keys" to "JWR66N" + value = value.split() + if len(value) > 1 and value[-1].endswith("-keys"): + value.pop() + value = " ".join(value) + line = key + "=" + value + if line != original_line: + print " replace: ", original_line + print " with: ", line + output.append(line) + return "\n".join(output) + "\n" + + +def ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info): + try: + keylist = input_tf_zip.read("META/otakeys.txt").split() + except KeyError: + raise common.ExternalError("can't read META/otakeys.txt from input") + + extra_recovery_keys = misc_info.get("extra_recovery_keys", None) + if extra_recovery_keys: + extra_recovery_keys = [OPTIONS.key_map.get(k, k) + ".x509.pem" + for k in extra_recovery_keys.split()] + if extra_recovery_keys: + print "extra recovery-only key(s): " + ", ".join(extra_recovery_keys) + else: + extra_recovery_keys = [] + + mapped_keys = [] + for k in keylist: + m = re.match(r"^(.*)\.x509\.pem$", k) + if not m: + raise common.ExternalError( + "can't parse \"%s\" from META/otakeys.txt" % (k,)) + k = m.group(1) + mapped_keys.append(OPTIONS.key_map.get(k, k) + ".x509.pem") + + if mapped_keys: + print "using:\n ", "\n ".join(mapped_keys) + print "for OTA package verification" + else: + devkey = misc_info.get("default_system_dev_certificate", + "build/target/product/security/testkey") + mapped_keys.append( + OPTIONS.key_map.get(devkey, devkey) + ".x509.pem") + print "META/otakeys.txt has no keys; using", mapped_keys[0] + + # recovery uses a version of the key that has been slightly + # predigested (by DumpPublicKey.java) and put in res/keys. + # extra_recovery_keys are used only in recovery. + + p = common.Run(["java", "-jar", + os.path.join(OPTIONS.search_path, "framework", "dumpkey.jar")] + + mapped_keys + extra_recovery_keys, + stdout=subprocess.PIPE) + new_recovery_keys, _ = p.communicate() + if p.returncode != 0: + raise common.ExternalError("failed to run dumpkeys") + common.ZipWriteStr(output_tf_zip, "RECOVERY/RAMDISK/res/keys", + new_recovery_keys) + + # SystemUpdateActivity uses the x509.pem version of the keys, but + # put into a zipfile system/etc/security/otacerts.zip. + # We DO NOT include the extra_recovery_keys (if any) here. + + temp_file = cStringIO.StringIO() + certs_zip = zipfile.ZipFile(temp_file, "w") + for k in mapped_keys: + certs_zip.write(k) + certs_zip.close() + common.ZipWriteStr(output_tf_zip, "SYSTEM/etc/security/otacerts.zip", + temp_file.getvalue()) + + return new_recovery_keys + +def ReplaceVerityPublicKey(targetfile_zip, key_path): + print "Replacing verity public key with %s" % key_path + with open(key_path) as f: + data = f.read() + common.ZipWriteStr(targetfile_zip, "BOOT/RAMDISK/verity_key", data) + return data + +def ReplaceVerityPrivateKey(targetfile_input_zip, targetfile_output_zip, + misc_info, key_path): + print "Replacing verity private key with %s" % key_path + current_key = misc_info["verity_key"] + original_misc_info = targetfile_input_zip.read("META/misc_info.txt") + new_misc_info = original_misc_info.replace(current_key, key_path) + common.ZipWriteStr(targetfile_output_zip, "META/misc_info.txt", new_misc_info) + misc_info["verity_key"] = key_path + +def BuildKeyMap(misc_info, key_mapping_options): + for s, d in key_mapping_options: + if s is None: # -d option + devkey = misc_info.get("default_system_dev_certificate", + "build/target/product/security/testkey") + devkeydir = os.path.dirname(devkey) + + OPTIONS.key_map.update({ + devkeydir + "/testkey": d + "/releasekey", + devkeydir + "/devkey": d + "/releasekey", + devkeydir + "/media": d + "/media", + devkeydir + "/shared": d + "/shared", + devkeydir + "/platform": d + "/platform", + }) + else: + OPTIONS.key_map[s] = d + + +def main(argv): + + key_mapping_options = [] + + def option_handler(o, a): + if o in ("-e", "--extra_apks"): + names, key = a.split("=") + names = names.split(",") + for n in names: + OPTIONS.extra_apks[n] = key + elif o in ("-d", "--default_key_mappings"): + key_mapping_options.append((None, a)) + elif o in ("-k", "--key_mapping"): + key_mapping_options.append(a.split("=", 1)) + elif o in ("-o", "--replace_ota_keys"): + OPTIONS.replace_ota_keys = True + elif o in ("-t", "--tag_changes"): + new = [] + for i in a.split(","): + i = i.strip() + if not i or i[0] not in "-+": + raise ValueError("Bad tag change '%s'" % (i,)) + new.append(i[0] + i[1:].strip()) + OPTIONS.tag_changes = tuple(new) + elif o == "--replace_verity_public_key": + OPTIONS.replace_verity_public_key = (True, a) + elif o == "--replace_verity_private_key": + OPTIONS.replace_verity_private_key = (True, a) + else: + return False + return True + + args = common.ParseOptions(argv, __doc__, + extra_opts="e:d:k:ot:", + extra_long_opts=["extra_apks=", + "default_key_mappings=", + "key_mapping=", + "replace_ota_keys", + "tag_changes=", + "replace_verity_public_key=", + "replace_verity_private_key="], + extra_option_handler=option_handler) + + if len(args) != 2: + common.Usage(__doc__) + sys.exit(1) + + input_zip = zipfile.ZipFile(args[0], "r") + output_zip = zipfile.ZipFile(args[1], "w") + + misc_info = common.LoadInfoDict(input_zip) + + BuildKeyMap(misc_info, key_mapping_options) + + apk_key_map = GetApkCerts(input_zip) + CheckAllApksSigned(input_zip, apk_key_map) + + key_passwords = common.GetKeyPasswords(set(apk_key_map.values())) + ProcessTargetFiles(input_zip, output_zip, misc_info, + apk_key_map, key_passwords) + + common.ZipClose(input_zip) + common.ZipClose(output_zip) + + add_img_to_target_files.AddImagesToTargetFiles(args[1]) + + print "done." + + +if __name__ == '__main__': + try: + main(sys.argv[1:]) + except common.ExternalError, e: + print + print " ERROR: %s" % (e,) + print + sys.exit(1) diff --git a/tools/releasetools/sign_target_files_apks.py b/tools/releasetools/sign_target_files_apks.py deleted file mode 100755 index ec49112..0000000 --- a/tools/releasetools/sign_target_files_apks.py +++ /dev/null @@ -1,506 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (C) 2008 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Signs all the APK files in a target-files zipfile, producing a new -target-files zip. - -Usage: sign_target_files_apks [flags] input_target_files output_target_files - - -e (--extra_apks) <name,name,...=key> - Add extra APK name/key pairs as though they appeared in - apkcerts.txt (so mappings specified by -k and -d are applied). - Keys specified in -e override any value for that app contained - in the apkcerts.txt file. Option may be repeated to give - multiple extra packages. - - -k (--key_mapping) <src_key=dest_key> - Add a mapping from the key name as specified in apkcerts.txt (the - src_key) to the real key you wish to sign the package with - (dest_key). Option may be repeated to give multiple key - mappings. - - -d (--default_key_mappings) <dir> - Set up the following key mappings: - - $devkey/devkey ==> $dir/releasekey - $devkey/testkey ==> $dir/releasekey - $devkey/media ==> $dir/media - $devkey/shared ==> $dir/shared - $devkey/platform ==> $dir/platform - - where $devkey is the directory part of the value of - default_system_dev_certificate from the input target-files's - META/misc_info.txt. (Defaulting to "build/target/product/security" - if the value is not present in misc_info. - - -d and -k options are added to the set of mappings in the order - in which they appear on the command line. - - -o (--replace_ota_keys) - Replace the certificate (public key) used by OTA package - verification with the one specified in the input target_files - zip (in the META/otakeys.txt file). Key remapping (-k and -d) - is performed on this key. - - -t (--tag_changes) <+tag>,<-tag>,... - Comma-separated list of changes to make to the set of tags (in - the last component of the build fingerprint). Prefix each with - '+' or '-' to indicate whether that tag should be added or - removed. Changes are processed in the order they appear. - Default value is "-test-keys,-dev-keys,+release-keys". - -""" - -import sys - -if sys.hexversion < 0x02070000: - print >> sys.stderr, "Python 2.7 or newer is required." - sys.exit(1) - -import base64 -import cStringIO -import copy -import errno -import os -import re -import shutil -import subprocess -import tempfile -import zipfile - -import add_img_to_target_files -import common - -OPTIONS = common.OPTIONS - -OPTIONS.extra_apks = {} -OPTIONS.key_map = {} -OPTIONS.replace_ota_keys = False -OPTIONS.replace_verity_public_key = False -OPTIONS.replace_verity_private_key = False -OPTIONS.tag_changes = ("-test-keys", "-dev-keys", "+release-keys") - -def GetApkCerts(tf_zip): - certmap = common.ReadApkCerts(tf_zip) - - # apply the key remapping to the contents of the file - for apk, cert in certmap.iteritems(): - certmap[apk] = OPTIONS.key_map.get(cert, cert) - - # apply all the -e options, overriding anything in the file - for apk, cert in OPTIONS.extra_apks.iteritems(): - if not cert: - cert = "PRESIGNED" - certmap[apk] = OPTIONS.key_map.get(cert, cert) - - return certmap - - -def CheckAllApksSigned(input_tf_zip, apk_key_map): - """Check that all the APKs we want to sign have keys specified, and - error out if they don't.""" - unknown_apks = [] - for info in input_tf_zip.infolist(): - if info.filename.endswith(".apk"): - name = os.path.basename(info.filename) - if name not in apk_key_map: - unknown_apks.append(name) - if unknown_apks: - print "ERROR: no key specified for:\n\n ", - print "\n ".join(unknown_apks) - print "\nUse '-e <apkname>=' to specify a key (which may be an" - print "empty string to not sign this apk)." - sys.exit(1) - - -def SignApk(data, keyname, pw): - unsigned = tempfile.NamedTemporaryFile() - unsigned.write(data) - unsigned.flush() - - signed = tempfile.NamedTemporaryFile() - - common.SignFile(unsigned.name, signed.name, keyname, pw, align=4) - - data = signed.read() - unsigned.close() - signed.close() - - return data - - -def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info, - apk_key_map, key_passwords): - - maxsize = max([len(os.path.basename(i.filename)) - for i in input_tf_zip.infolist() - if i.filename.endswith('.apk')]) - rebuild_recovery = False - - tmpdir = tempfile.mkdtemp() - def write_to_temp(fn, attr, data): - fn = os.path.join(tmpdir, fn) - if fn.endswith("/"): - fn = os.path.join(tmpdir, fn) - os.mkdir(fn) - else: - d = os.path.dirname(fn) - if d and not os.path.exists(d): - os.makedirs(d) - - if attr >> 16 == 0xa1ff: - os.symlink(data, fn) - else: - with open(fn, "wb") as f: - f.write(data) - - for info in input_tf_zip.infolist(): - if info.filename.startswith("IMAGES/"): - continue - - data = input_tf_zip.read(info.filename) - out_info = copy.copy(info) - - if (info.filename == "META/misc_info.txt" and - OPTIONS.replace_verity_private_key): - ReplaceVerityPrivateKey(input_tf_zip, output_tf_zip, misc_info, - OPTIONS.replace_verity_private_key[1]) - elif (info.filename == "BOOT/RAMDISK/verity_key" and - OPTIONS.replace_verity_public_key): - new_data = ReplaceVerityPublicKey(output_tf_zip, - OPTIONS.replace_verity_public_key[1]) - write_to_temp(info.filename, info.external_attr, new_data) - elif (info.filename.startswith("BOOT/") or - info.filename.startswith("RECOVERY/") or - info.filename.startswith("META/") or - info.filename == "SYSTEM/etc/recovery-resource.dat"): - write_to_temp(info.filename, info.external_attr, data) - - if info.filename.endswith(".apk"): - name = os.path.basename(info.filename) - key = apk_key_map[name] - if key not in common.SPECIAL_CERT_STRINGS: - print " signing: %-*s (%s)" % (maxsize, name, key) - signed_data = SignApk(data, key, key_passwords[key]) - common.ZipWriteStr(output_tf_zip, out_info, signed_data) - else: - # an APK we're not supposed to sign. - print "NOT signing: %s" % (name,) - common.ZipWriteStr(output_tf_zip, out_info, data) - elif info.filename in ("SYSTEM/build.prop", - "VENDOR/build.prop", - "RECOVERY/RAMDISK/default.prop"): - print "rewriting %s:" % (info.filename,) - new_data = RewriteProps(data, misc_info) - common.ZipWriteStr(output_tf_zip, out_info, new_data) - if info.filename == "RECOVERY/RAMDISK/default.prop": - write_to_temp(info.filename, info.external_attr, new_data) - elif info.filename.endswith("mac_permissions.xml"): - print "rewriting %s with new keys." % (info.filename,) - new_data = ReplaceCerts(data) - common.ZipWriteStr(output_tf_zip, out_info, new_data) - elif info.filename in ("SYSTEM/recovery-from-boot.p", - "SYSTEM/bin/install-recovery.sh"): - rebuild_recovery = True - elif (OPTIONS.replace_ota_keys and - info.filename in ("RECOVERY/RAMDISK/res/keys", - "SYSTEM/etc/security/otacerts.zip")): - # don't copy these files if we're regenerating them below - pass - elif (OPTIONS.replace_verity_private_key and - info.filename == "META/misc_info.txt"): - pass - elif (OPTIONS.replace_verity_public_key and - info.filename == "BOOT/RAMDISK/verity_key"): - pass - else: - # a non-APK file; copy it verbatim - common.ZipWriteStr(output_tf_zip, out_info, data) - - if OPTIONS.replace_ota_keys: - new_recovery_keys = ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info) - if new_recovery_keys: - write_to_temp("RECOVERY/RAMDISK/res/keys", 0o755 << 16, new_recovery_keys) - - if rebuild_recovery: - recovery_img = common.GetBootableImage( - "recovery.img", "recovery.img", tmpdir, "RECOVERY", info_dict=misc_info) - boot_img = common.GetBootableImage( - "boot.img", "boot.img", tmpdir, "BOOT", info_dict=misc_info) - - def output_sink(fn, data): - common.ZipWriteStr(output_tf_zip, "SYSTEM/" + fn, data) - - common.MakeRecoveryPatch(tmpdir, output_sink, recovery_img, boot_img, - info_dict=misc_info) - - shutil.rmtree(tmpdir) - - -def ReplaceCerts(data): - """Given a string of data, replace all occurences of a set - of X509 certs with a newer set of X509 certs and return - the updated data string.""" - for old, new in OPTIONS.key_map.iteritems(): - try: - if OPTIONS.verbose: - print " Replacing %s.x509.pem with %s.x509.pem" % (old, new) - f = open(old + ".x509.pem") - old_cert16 = base64.b16encode(common.ParseCertificate(f.read())).lower() - f.close() - f = open(new + ".x509.pem") - new_cert16 = base64.b16encode(common.ParseCertificate(f.read())).lower() - f.close() - # Only match entire certs. - pattern = "\\b"+old_cert16+"\\b" - (data, num) = re.subn(pattern, new_cert16, data, flags=re.IGNORECASE) - if OPTIONS.verbose: - print " Replaced %d occurence(s) of %s.x509.pem with " \ - "%s.x509.pem" % (num, old, new) - except IOError as e: - if e.errno == errno.ENOENT and not OPTIONS.verbose: - continue - - print " Error accessing %s. %s. Skip replacing %s.x509.pem " \ - "with %s.x509.pem." % (e.filename, e.strerror, old, new) - - return data - - -def EditTags(tags): - """Given a string containing comma-separated tags, apply the edits - specified in OPTIONS.tag_changes and return the updated string.""" - tags = set(tags.split(",")) - for ch in OPTIONS.tag_changes: - if ch[0] == "-": - tags.discard(ch[1:]) - elif ch[0] == "+": - tags.add(ch[1:]) - return ",".join(sorted(tags)) - - -def RewriteProps(data, misc_info): - output = [] - for line in data.split("\n"): - line = line.strip() - original_line = line - if line and line[0] != '#' and "=" in line: - key, value = line.split("=", 1) - if (key in ("ro.build.fingerprint", "ro.vendor.build.fingerprint") - and misc_info.get("oem_fingerprint_properties") is None): - pieces = value.split("/") - pieces[-1] = EditTags(pieces[-1]) - value = "/".join(pieces) - elif (key in ("ro.build.thumbprint", "ro.vendor.build.thumbprint") - and misc_info.get("oem_fingerprint_properties") is not None): - pieces = value.split("/") - pieces[-1] = EditTags(pieces[-1]) - value = "/".join(pieces) - elif key == "ro.build.description": - pieces = value.split(" ") - assert len(pieces) == 5 - pieces[-1] = EditTags(pieces[-1]) - value = " ".join(pieces) - elif key == "ro.build.tags": - value = EditTags(value) - elif key == "ro.build.display.id": - # change, eg, "JWR66N dev-keys" to "JWR66N" - value = value.split() - if len(value) > 1 and value[-1].endswith("-keys"): - value.pop() - value = " ".join(value) - line = key + "=" + value - if line != original_line: - print " replace: ", original_line - print " with: ", line - output.append(line) - return "\n".join(output) + "\n" - - -def ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info): - try: - keylist = input_tf_zip.read("META/otakeys.txt").split() - except KeyError: - raise common.ExternalError("can't read META/otakeys.txt from input") - - extra_recovery_keys = misc_info.get("extra_recovery_keys", None) - if extra_recovery_keys: - extra_recovery_keys = [OPTIONS.key_map.get(k, k) + ".x509.pem" - for k in extra_recovery_keys.split()] - if extra_recovery_keys: - print "extra recovery-only key(s): " + ", ".join(extra_recovery_keys) - else: - extra_recovery_keys = [] - - mapped_keys = [] - for k in keylist: - m = re.match(r"^(.*)\.x509\.pem$", k) - if not m: - raise common.ExternalError( - "can't parse \"%s\" from META/otakeys.txt" % (k,)) - k = m.group(1) - mapped_keys.append(OPTIONS.key_map.get(k, k) + ".x509.pem") - - if mapped_keys: - print "using:\n ", "\n ".join(mapped_keys) - print "for OTA package verification" - else: - devkey = misc_info.get("default_system_dev_certificate", - "build/target/product/security/testkey") - mapped_keys.append( - OPTIONS.key_map.get(devkey, devkey) + ".x509.pem") - print "META/otakeys.txt has no keys; using", mapped_keys[0] - - # recovery uses a version of the key that has been slightly - # predigested (by DumpPublicKey.java) and put in res/keys. - # extra_recovery_keys are used only in recovery. - - p = common.Run(["java", "-jar", - os.path.join(OPTIONS.search_path, "framework", "dumpkey.jar")] - + mapped_keys + extra_recovery_keys, - stdout=subprocess.PIPE) - new_recovery_keys, _ = p.communicate() - if p.returncode != 0: - raise common.ExternalError("failed to run dumpkeys") - common.ZipWriteStr(output_tf_zip, "RECOVERY/RAMDISK/res/keys", - new_recovery_keys) - - # SystemUpdateActivity uses the x509.pem version of the keys, but - # put into a zipfile system/etc/security/otacerts.zip. - # We DO NOT include the extra_recovery_keys (if any) here. - - temp_file = cStringIO.StringIO() - certs_zip = zipfile.ZipFile(temp_file, "w") - for k in mapped_keys: - certs_zip.write(k) - certs_zip.close() - common.ZipWriteStr(output_tf_zip, "SYSTEM/etc/security/otacerts.zip", - temp_file.getvalue()) - - return new_recovery_keys - -def ReplaceVerityPublicKey(targetfile_zip, key_path): - print "Replacing verity public key with %s" % key_path - with open(key_path) as f: - data = f.read() - common.ZipWriteStr(targetfile_zip, "BOOT/RAMDISK/verity_key", data) - return data - -def ReplaceVerityPrivateKey(targetfile_input_zip, targetfile_output_zip, - misc_info, key_path): - print "Replacing verity private key with %s" % key_path - current_key = misc_info["verity_key"] - original_misc_info = targetfile_input_zip.read("META/misc_info.txt") - new_misc_info = original_misc_info.replace(current_key, key_path) - common.ZipWriteStr(targetfile_output_zip, "META/misc_info.txt", new_misc_info) - misc_info["verity_key"] = key_path - -def BuildKeyMap(misc_info, key_mapping_options): - for s, d in key_mapping_options: - if s is None: # -d option - devkey = misc_info.get("default_system_dev_certificate", - "build/target/product/security/testkey") - devkeydir = os.path.dirname(devkey) - - OPTIONS.key_map.update({ - devkeydir + "/testkey": d + "/releasekey", - devkeydir + "/devkey": d + "/releasekey", - devkeydir + "/media": d + "/media", - devkeydir + "/shared": d + "/shared", - devkeydir + "/platform": d + "/platform", - }) - else: - OPTIONS.key_map[s] = d - - -def main(argv): - - key_mapping_options = [] - - def option_handler(o, a): - if o in ("-e", "--extra_apks"): - names, key = a.split("=") - names = names.split(",") - for n in names: - OPTIONS.extra_apks[n] = key - elif o in ("-d", "--default_key_mappings"): - key_mapping_options.append((None, a)) - elif o in ("-k", "--key_mapping"): - key_mapping_options.append(a.split("=", 1)) - elif o in ("-o", "--replace_ota_keys"): - OPTIONS.replace_ota_keys = True - elif o in ("-t", "--tag_changes"): - new = [] - for i in a.split(","): - i = i.strip() - if not i or i[0] not in "-+": - raise ValueError("Bad tag change '%s'" % (i,)) - new.append(i[0] + i[1:].strip()) - OPTIONS.tag_changes = tuple(new) - elif o == "--replace_verity_public_key": - OPTIONS.replace_verity_public_key = (True, a) - elif o == "--replace_verity_private_key": - OPTIONS.replace_verity_private_key = (True, a) - else: - return False - return True - - args = common.ParseOptions(argv, __doc__, - extra_opts="e:d:k:ot:", - extra_long_opts=["extra_apks=", - "default_key_mappings=", - "key_mapping=", - "replace_ota_keys", - "tag_changes=", - "replace_verity_public_key=", - "replace_verity_private_key="], - extra_option_handler=option_handler) - - if len(args) != 2: - common.Usage(__doc__) - sys.exit(1) - - input_zip = zipfile.ZipFile(args[0], "r") - output_zip = zipfile.ZipFile(args[1], "w") - - misc_info = common.LoadInfoDict(input_zip) - - BuildKeyMap(misc_info, key_mapping_options) - - apk_key_map = GetApkCerts(input_zip) - CheckAllApksSigned(input_zip, apk_key_map) - - key_passwords = common.GetKeyPasswords(set(apk_key_map.values())) - ProcessTargetFiles(input_zip, output_zip, misc_info, - apk_key_map, key_passwords) - - common.ZipClose(input_zip) - common.ZipClose(output_zip) - - add_img_to_target_files.AddImagesToTargetFiles(args[1]) - - print "done." - - -if __name__ == '__main__': - try: - main(sys.argv[1:]) - except common.ExternalError, e: - print - print " ERROR: %s" % (e,) - print - sys.exit(1) diff --git a/tools/releasetools/sign_target_files_apks.tmp b/tools/releasetools/sign_target_files_apks.tmp new file mode 120000 index 0000000..b5ec59a --- /dev/null +++ b/tools/releasetools/sign_target_files_apks.tmp @@ -0,0 +1 @@ +sign_target_files_apks.py
\ No newline at end of file |