1*e4a36f41SAndroid Build Coastguard Worker#!/usr/bin/env python 2*e4a36f41SAndroid Build Coastguard Worker# 3*e4a36f41SAndroid Build Coastguard Worker# Copyright (C) 2013 The Android Open Source Project 4*e4a36f41SAndroid Build Coastguard Worker# 5*e4a36f41SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*e4a36f41SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*e4a36f41SAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*e4a36f41SAndroid Build Coastguard Worker# 9*e4a36f41SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*e4a36f41SAndroid Build Coastguard Worker# 11*e4a36f41SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*e4a36f41SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*e4a36f41SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*e4a36f41SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*e4a36f41SAndroid Build Coastguard Worker# limitations under the License. 16*e4a36f41SAndroid Build Coastguard Worker 17*e4a36f41SAndroid Build Coastguard Worker""" 18*e4a36f41SAndroid Build Coastguard WorkerTool to help modify an existing mac_permissions.xml with additional app 19*e4a36f41SAndroid Build Coastguard Workercerts not already found in that policy. This becomes useful when a directory 20*e4a36f41SAndroid Build Coastguard Workercontaining apps is searched and the certs from those apps are added to the 21*e4a36f41SAndroid Build Coastguard Workerpolicy not already explicitly listed. 22*e4a36f41SAndroid Build Coastguard Worker""" 23*e4a36f41SAndroid Build Coastguard Worker 24*e4a36f41SAndroid Build Coastguard Workerimport sys 25*e4a36f41SAndroid Build Coastguard Workerimport os 26*e4a36f41SAndroid Build Coastguard Workerimport argparse 27*e4a36f41SAndroid Build Coastguard Workerfrom base64 import b16encode, b64decode 28*e4a36f41SAndroid Build Coastguard Workerimport fileinput 29*e4a36f41SAndroid Build Coastguard Workerimport re 30*e4a36f41SAndroid Build Coastguard Workerimport subprocess 31*e4a36f41SAndroid Build Coastguard Workerimport zipfile 32*e4a36f41SAndroid Build Coastguard Worker 33*e4a36f41SAndroid Build Coastguard WorkerPEM_CERT_RE = """-----BEGIN CERTIFICATE----- 34*e4a36f41SAndroid Build Coastguard Worker(.+?) 35*e4a36f41SAndroid Build Coastguard Worker-----END CERTIFICATE----- 36*e4a36f41SAndroid Build Coastguard Worker""" 37*e4a36f41SAndroid Build Coastguard Workerdef collect_certs_for_app(filename): 38*e4a36f41SAndroid Build Coastguard Worker app_certs = set() 39*e4a36f41SAndroid Build Coastguard Worker with zipfile.ZipFile(filename, 'r') as apkzip: 40*e4a36f41SAndroid Build Coastguard Worker for info in apkzip.infolist(): 41*e4a36f41SAndroid Build Coastguard Worker name = info.filename 42*e4a36f41SAndroid Build Coastguard Worker if name.startswith('META-INF/') and name.endswith(('.DSA', '.RSA')): 43*e4a36f41SAndroid Build Coastguard Worker cmd = ['openssl', 'pkcs7', '-inform', 'DER', 44*e4a36f41SAndroid Build Coastguard Worker '-outform', 'PEM', '-print_certs'] 45*e4a36f41SAndroid Build Coastguard Worker p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, 46*e4a36f41SAndroid Build Coastguard Worker stderr=subprocess.PIPE) 47*e4a36f41SAndroid Build Coastguard Worker pem_string, err = p.communicate(apkzip.read(name)) 48*e4a36f41SAndroid Build Coastguard Worker if err and err.strip(): 49*e4a36f41SAndroid Build Coastguard Worker raise RuntimeError('Problem running openssl on %s (%s)' % (filename, e)) 50*e4a36f41SAndroid Build Coastguard Worker 51*e4a36f41SAndroid Build Coastguard Worker # turn multiline base64 to single line base16 52*e4a36f41SAndroid Build Coastguard Worker transform = lambda x: b16encode(b64decode(x.replace('\n', ''))).lower() 53*e4a36f41SAndroid Build Coastguard Worker results = re.findall(PEM_CERT_RE, pem_string, re.DOTALL) 54*e4a36f41SAndroid Build Coastguard Worker certs = [transform(i) for i in results] 55*e4a36f41SAndroid Build Coastguard Worker 56*e4a36f41SAndroid Build Coastguard Worker app_certs.update(certs) 57*e4a36f41SAndroid Build Coastguard Worker 58*e4a36f41SAndroid Build Coastguard Worker return app_certs 59*e4a36f41SAndroid Build Coastguard Worker 60*e4a36f41SAndroid Build Coastguard Workerdef add_leftover_certs(args): 61*e4a36f41SAndroid Build Coastguard Worker all_app_certs = set() 62*e4a36f41SAndroid Build Coastguard Worker for dirpath, _, files in os.walk(args.dir): 63*e4a36f41SAndroid Build Coastguard Worker transform = lambda x: os.path.join(dirpath, x) 64*e4a36f41SAndroid Build Coastguard Worker condition = lambda x: x.endswith('.apk') 65*e4a36f41SAndroid Build Coastguard Worker apps = [transform(i) for i in files if condition(i)] 66*e4a36f41SAndroid Build Coastguard Worker 67*e4a36f41SAndroid Build Coastguard Worker # Collect certs for each app found 68*e4a36f41SAndroid Build Coastguard Worker for app in apps: 69*e4a36f41SAndroid Build Coastguard Worker app_certs = collect_certs_for_app(app) 70*e4a36f41SAndroid Build Coastguard Worker all_app_certs.update(app_certs) 71*e4a36f41SAndroid Build Coastguard Worker 72*e4a36f41SAndroid Build Coastguard Worker if all_app_certs: 73*e4a36f41SAndroid Build Coastguard Worker policy_certs = set() 74*e4a36f41SAndroid Build Coastguard Worker with open(args.policy, 'r') as f: 75*e4a36f41SAndroid Build Coastguard Worker cert_pattern = 'signature="([a-fA-F0-9]+)"' 76*e4a36f41SAndroid Build Coastguard Worker policy_certs = re.findall(cert_pattern, f.read()) 77*e4a36f41SAndroid Build Coastguard Worker 78*e4a36f41SAndroid Build Coastguard Worker cert_diff = all_app_certs.difference(policy_certs) 79*e4a36f41SAndroid Build Coastguard Worker 80*e4a36f41SAndroid Build Coastguard Worker # Build xml stanzas 81*e4a36f41SAndroid Build Coastguard Worker inner_tag = '<seinfo value="%s"/>' % args.seinfo 82*e4a36f41SAndroid Build Coastguard Worker stanza = '<signer signature="%s">%s</signer>' 83*e4a36f41SAndroid Build Coastguard Worker new_stanzas = [stanza % (cert, inner_tag) for cert in cert_diff] 84*e4a36f41SAndroid Build Coastguard Worker mac_perms_string = ''.join(new_stanzas) 85*e4a36f41SAndroid Build Coastguard Worker mac_perms_string += '</policy>' 86*e4a36f41SAndroid Build Coastguard Worker 87*e4a36f41SAndroid Build Coastguard Worker # Inline replace with new policy stanzas 88*e4a36f41SAndroid Build Coastguard Worker for line in fileinput.input(args.policy, inplace=True): 89*e4a36f41SAndroid Build Coastguard Worker sys.stdout.write(line.replace('</policy>', mac_perms_string)) 90*e4a36f41SAndroid Build Coastguard Worker 91*e4a36f41SAndroid Build Coastguard Workerdef main(argv): 92*e4a36f41SAndroid Build Coastguard Worker parser = argparse.ArgumentParser(description=__doc__) 93*e4a36f41SAndroid Build Coastguard Worker 94*e4a36f41SAndroid Build Coastguard Worker parser.add_argument('-s', '--seinfo', dest='seinfo', required=True, 95*e4a36f41SAndroid Build Coastguard Worker help='seinfo tag for each generated stanza') 96*e4a36f41SAndroid Build Coastguard Worker parser.add_argument('-d', '--dir', dest='dir', required=True, 97*e4a36f41SAndroid Build Coastguard Worker help='Directory to search for apks') 98*e4a36f41SAndroid Build Coastguard Worker parser.add_argument('-f', '--file', dest='policy', required=True, 99*e4a36f41SAndroid Build Coastguard Worker help='mac_permissions.xml policy file') 100*e4a36f41SAndroid Build Coastguard Worker 101*e4a36f41SAndroid Build Coastguard Worker parser.set_defaults(func=add_leftover_certs) 102*e4a36f41SAndroid Build Coastguard Worker args = parser.parse_args() 103*e4a36f41SAndroid Build Coastguard Worker args.func(args) 104*e4a36f41SAndroid Build Coastguard Worker 105*e4a36f41SAndroid Build Coastguard Workerif __name__ == '__main__': 106*e4a36f41SAndroid Build Coastguard Worker main(sys.argv) 107