1#!/usr/bin/env python 2# 3# Copyright (C) 2013 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""" 18Tool to help modify an existing mac_permissions.xml with additional app 19certs not already found in that policy. This becomes useful when a directory 20containing apps is searched and the certs from those apps are added to the 21policy not already explicitly listed. 22""" 23 24import sys 25import os 26import argparse 27from base64 import b16encode, b64decode 28import fileinput 29import re 30import subprocess 31import zipfile 32 33PEM_CERT_RE = """-----BEGIN CERTIFICATE----- 34(.+?) 35-----END CERTIFICATE----- 36""" 37def collect_certs_for_app(filename): 38 app_certs = set() 39 with zipfile.ZipFile(filename, 'r') as apkzip: 40 for info in apkzip.infolist(): 41 name = info.filename 42 if name.startswith('META-INF/') and name.endswith(('.DSA', '.RSA')): 43 cmd = ['openssl', 'pkcs7', '-inform', 'DER', 44 '-outform', 'PEM', '-print_certs'] 45 p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, 46 stderr=subprocess.PIPE) 47 pem_string, err = p.communicate(apkzip.read(name)) 48 if err and err.strip(): 49 raise RuntimeError('Problem running openssl on %s (%s)' % (filename, e)) 50 51 # turn multiline base64 to single line base16 52 transform = lambda x: b16encode(b64decode(x.replace('\n', ''))).lower() 53 results = re.findall(PEM_CERT_RE, pem_string, re.DOTALL) 54 certs = [transform(i) for i in results] 55 56 app_certs.update(certs) 57 58 return app_certs 59 60def add_leftover_certs(args): 61 all_app_certs = set() 62 for dirpath, _, files in os.walk(args.dir): 63 transform = lambda x: os.path.join(dirpath, x) 64 condition = lambda x: x.endswith('.apk') 65 apps = [transform(i) for i in files if condition(i)] 66 67 # Collect certs for each app found 68 for app in apps: 69 app_certs = collect_certs_for_app(app) 70 all_app_certs.update(app_certs) 71 72 if all_app_certs: 73 policy_certs = set() 74 with open(args.policy, 'r') as f: 75 cert_pattern = 'signature="([a-fA-F0-9]+)"' 76 policy_certs = re.findall(cert_pattern, f.read()) 77 78 cert_diff = all_app_certs.difference(policy_certs) 79 80 # Build xml stanzas 81 inner_tag = '<seinfo value="%s"/>' % args.seinfo 82 stanza = '<signer signature="%s">%s</signer>' 83 new_stanzas = [stanza % (cert, inner_tag) for cert in cert_diff] 84 mac_perms_string = ''.join(new_stanzas) 85 mac_perms_string += '</policy>' 86 87 # Inline replace with new policy stanzas 88 for line in fileinput.input(args.policy, inplace=True): 89 sys.stdout.write(line.replace('</policy>', mac_perms_string)) 90 91def main(argv): 92 parser = argparse.ArgumentParser(description=__doc__) 93 94 parser.add_argument('-s', '--seinfo', dest='seinfo', required=True, 95 help='seinfo tag for each generated stanza') 96 parser.add_argument('-d', '--dir', dest='dir', required=True, 97 help='Directory to search for apks') 98 parser.add_argument('-f', '--file', dest='policy', required=True, 99 help='mac_permissions.xml policy file') 100 101 parser.set_defaults(func=add_leftover_certs) 102 args = parser.parse_args() 103 args.func(args) 104 105if __name__ == '__main__': 106 main(sys.argv) 107