| #!/usr/bin/env python |
| # |
| # Copyright (C) 2013 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. |
| |
| """ |
| Tool to help modify an existing mac_permissions.xml with additional app |
| certs not already found in that policy. This becomes useful when a directory |
| containing apps is searched and the certs from those apps are added to the |
| policy not already explicitly listed. |
| """ |
| |
| import sys |
| import os |
| import argparse |
| from base64 import b16encode, b64decode |
| import fileinput |
| import re |
| import subprocess |
| import zipfile |
| |
| PEM_CERT_RE = """-----BEGIN CERTIFICATE----- |
| (.+?) |
| -----END CERTIFICATE----- |
| """ |
| def collect_certs_for_app(filename): |
| app_certs = set() |
| with zipfile.ZipFile(filename, 'r') as apkzip: |
| for info in apkzip.infolist(): |
| name = info.filename |
| if name.startswith('META-INF/') and name.endswith(('.DSA', '.RSA')): |
| cmd = ['openssl', 'pkcs7', '-inform', 'DER', |
| '-outform', 'PEM', '-print_certs'] |
| p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE) |
| pem_string, err = p.communicate(apkzip.read(name)) |
| if err and err.strip(): |
| raise RuntimeError('Problem running openssl on %s (%s)' % (filename, e)) |
| |
| # turn multiline base64 to single line base16 |
| transform = lambda x: b16encode(b64decode(x.replace('\n', ''))).lower() |
| results = re.findall(PEM_CERT_RE, pem_string, re.DOTALL) |
| certs = [transform(i) for i in results] |
| |
| app_certs.update(certs) |
| |
| return app_certs |
| |
| def add_leftover_certs(args): |
| all_app_certs = set() |
| for dirpath, _, files in os.walk(args.dir): |
| transform = lambda x: os.path.join(dirpath, x) |
| condition = lambda x: x.endswith('.apk') |
| apps = [transform(i) for i in files if condition(i)] |
| |
| # Collect certs for each app found |
| for app in apps: |
| app_certs = collect_certs_for_app(app) |
| all_app_certs.update(app_certs) |
| |
| if all_app_certs: |
| policy_certs = set() |
| with open(args.policy, 'r') as f: |
| cert_pattern = 'signature="([a-fA-F0-9]+)"' |
| policy_certs = re.findall(cert_pattern, f.read()) |
| |
| cert_diff = all_app_certs.difference(policy_certs) |
| |
| # Build xml stanzas |
| inner_tag = '<seinfo value="%s"/>' % args.seinfo |
| stanza = '<signer signature="%s">%s</signer>' |
| new_stanzas = [stanza % (cert, inner_tag) for cert in cert_diff] |
| mac_perms_string = ''.join(new_stanzas) |
| mac_perms_string += '</policy>' |
| |
| # Inline replace with new policy stanzas |
| for line in fileinput.input(args.policy, inplace=True): |
| sys.stdout.write(line.replace('</policy>', mac_perms_string)) |
| |
| def main(argv): |
| parser = argparse.ArgumentParser(description=__doc__) |
| |
| parser.add_argument('-s', '--seinfo', dest='seinfo', required=True, |
| help='seinfo tag for each generated stanza') |
| parser.add_argument('-d', '--dir', dest='dir', required=True, |
| help='Directory to search for apks') |
| parser.add_argument('-f', '--file', dest='policy', required=True, |
| help='mac_permissions.xml policy file') |
| |
| parser.set_defaults(func=add_leftover_certs) |
| args = parser.parse_args() |
| args.func(args) |
| |
| if __name__ == '__main__': |
| main(sys.argv) |