1#!/usr/bin/env python 2 3from xml.sax import saxutils, handler, make_parser 4from optparse import OptionParser 5import ConfigParser 6import logging 7import base64 8import sys 9import os 10 11__VERSION = (0, 1) 12 13''' 14This tool reads a mac_permissions.xml and replaces keywords in the signature 15clause with keys provided by pem files. 16''' 17 18class GenerateKeys(object): 19 def __init__(self, path): 20 ''' 21 Generates an object with Base16 and Base64 encoded versions of the keys 22 found in the supplied pem file argument. PEM files can contain multiple 23 certs, however this seems to be unused in Android as pkg manager grabs 24 the first cert in the APK. This will however support multiple certs in 25 the resulting generation with index[0] being the first cert in the pem 26 file. 27 ''' 28 29 self._base64Key = list() 30 self._base16Key = list() 31 32 if not os.path.isfile(path): 33 sys.exit("Path " + path + " does not exist or is not a file!") 34 35 pkFile = open(path, 'rb').readlines() 36 base64Key = "" 37 inCert = False 38 for line in pkFile: 39 if line.startswith("-"): 40 inCert = not inCert 41 continue 42 43 base64Key += line.strip() 44 45 # Base 64 includes uppercase. DO NOT tolower() 46 self._base64Key.append(base64Key) 47 48 # Pkgmanager and setool see hex strings with lowercase, lets be consistent. 49 self._base16Key.append(base64.b16encode(base64.b64decode(base64Key)).lower()) 50 51 def __len__(self): 52 return len(self._base16Key) 53 54 def __str__(self): 55 return str(self.getBase16Keys()) 56 57 def getBase16Keys(self): 58 return self._base16Key 59 60 def getBase64Keys(self): 61 return self._base64Key 62 63class ParseConfig(ConfigParser.ConfigParser): 64 65 # This must be lowercase 66 OPTION_WILDCARD_TAG = "all" 67 68 def generateKeyMap(self, target_build_variant, key_directory): 69 70 keyMap = dict() 71 72 for tag in self.sections(): 73 74 options = self.options(tag) 75 76 for option in options: 77 78 # Only generate the key map for debug or release, 79 # not both! 80 if option != target_build_variant and \ 81 option != ParseConfig.OPTION_WILDCARD_TAG: 82 logging.info("Skipping " + tag + " : " + option + 83 " because target build variant is set to " + 84 str(target_build_variant)) 85 continue 86 87 if tag in keyMap: 88 sys.exit("Duplicate tag detected " + tag) 89 90 path = os.path.join(key_directory, self.get(tag, option)) 91 92 keyMap[tag] = GenerateKeys(path) 93 94 # Multiple certificates may exist in 95 # the pem file. GenerateKeys supports 96 # this however, the mac_permissions.xml 97 # as well as PMS do not. 98 assert len(keyMap[tag]) == 1 99 100 return keyMap 101 102class ReplaceTags(handler.ContentHandler): 103 104 DEFAULT_TAG = "default" 105 PACKAGE_TAG = "package" 106 POLICY_TAG = "policy" 107 SIGNER_TAG = "signer" 108 SIGNATURE_TAG = "signature" 109 110 TAGS_WITH_CHILDREN = [ DEFAULT_TAG, PACKAGE_TAG, POLICY_TAG, SIGNER_TAG ] 111 112 XML_ENCODING_TAG = '<?xml version="1.0" encoding="iso-8859-1"?>' 113 114 def __init__(self, keyMap, out=sys.stdout): 115 116 handler.ContentHandler.__init__(self) 117 self._keyMap = keyMap 118 self._out = out 119 self._out.write(ReplaceTags.XML_ENCODING_TAG) 120 self._out.write("<!-- AUTOGENERATED FILE DO NOT MODIFY -->") 121 self._out.write("<policy>") 122 123 def __del__(self): 124 self._out.write("</policy>") 125 126 def startElement(self, tag, attrs): 127 if tag == ReplaceTags.POLICY_TAG: 128 return 129 130 self._out.write('<' + tag) 131 132 for (name, value) in attrs.items(): 133 134 if name == ReplaceTags.SIGNATURE_TAG and value in self._keyMap: 135 for key in self._keyMap[value].getBase16Keys(): 136 logging.info("Replacing " + name + " " + value + " with " + key) 137 self._out.write(' %s="%s"' % (name, saxutils.escape(key))) 138 else: 139 self._out.write(' %s="%s"' % (name, saxutils.escape(value))) 140 141 if tag in ReplaceTags.TAGS_WITH_CHILDREN: 142 self._out.write('>') 143 else: 144 self._out.write('/>') 145 146 def endElement(self, tag): 147 if tag == ReplaceTags.POLICY_TAG: 148 return 149 150 if tag in ReplaceTags.TAGS_WITH_CHILDREN: 151 self._out.write('</%s>' % tag) 152 153 def characters(self, content): 154 if not content.isspace(): 155 self._out.write(saxutils.escape(content)) 156 157 def ignorableWhitespace(self, content): 158 pass 159 160 def processingInstruction(self, target, data): 161 self._out.write('<?%s %s?>' % (target, data)) 162 163if __name__ == "__main__": 164 165 # Intentional double space to line up equls signs and opening " for 166 # readability. 167 usage = "usage: %prog [options] CONFIG_FILE MAC_PERMISSIONS_FILE [MAC_PERMISSIONS_FILE...]\n" 168 usage += "This tool allows one to configure an automatic inclusion\n" 169 usage += "of signing keys into the mac_permision.xml file(s) from the\n" 170 usage += "pem files. If mulitple mac_permision.xml files are included\n" 171 usage += "then they are unioned to produce a final version." 172 173 version = "%prog " + str(__VERSION) 174 175 parser = OptionParser(usage=usage, version=version) 176 177 parser.add_option("-v", "--verbose", 178 action="store_true", dest="verbose", default=False, 179 help="Print internal operations to stdout") 180 181 parser.add_option("-o", "--output", default="stdout", dest="output_file", 182 metavar="FILE", help="Specify an output file, default is stdout") 183 184 parser.add_option("-c", "--cwd", default=os.getcwd(), dest="root", 185 metavar="DIR", help="Specify a root (CWD) directory to run this from, it" \ 186 "chdirs' AFTER loading the config file") 187 188 parser.add_option("-t", "--target-build-variant", default="eng", dest="target_build_variant", 189 help="Specify the TARGET_BUILD_VARIANT, defaults to eng") 190 191 parser.add_option("-d", "--key-directory", default="", dest="key_directory", 192 help="Specify a parent directory for keys") 193 194 (options, args) = parser.parse_args() 195 196 if len(args) < 2: 197 parser.error("Must specify a config file (keys.conf) AND mac_permissions.xml file(s)!") 198 199 logging.basicConfig(level=logging.INFO if options.verbose == True else logging.WARN) 200 201 # Read the config file 202 config = ParseConfig() 203 config.read(args[0]) 204 205 os.chdir(options.root) 206 207 output_file = sys.stdout if options.output_file == "stdout" else open(options.output_file, "w") 208 logging.info("Setting output file to: " + options.output_file) 209 210 # Generate the key list 211 key_map = config.generateKeyMap(options.target_build_variant.lower(), options.key_directory) 212 logging.info("Generate key map:") 213 for k in key_map: 214 logging.info(k + " : " + str(key_map[k])) 215 # Generate the XML file with markup replaced with keys 216 parser = make_parser() 217 parser.setContentHandler(ReplaceTags(key_map, output_file)) 218 for f in args[1:]: 219 parser.parse(f) 220