Home | History | Annotate | Download | only in tools
      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 """
     18 Tool to help modify an existing mac_permissions.xml with additional app
     19 certs not already found in that policy. This becomes useful when a directory
     20 containing apps is searched and the certs from those apps are added to the
     21 policy not already explicitly listed.
     22 """
     23 
     24 import sys
     25 import os
     26 import argparse
     27 from base64 import b16encode, b64decode
     28 import fileinput
     29 import re
     30 import subprocess
     31 import zipfile
     32 
     33 PEM_CERT_RE = """-----BEGIN CERTIFICATE-----
     34 (.+?)
     35 -----END CERTIFICATE-----
     36 """
     37 def 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 
     60 def 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 
     91 def 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 
    105 if __name__ == '__main__':
    106   main(sys.argv)
    107