blob: dec6d87722d523c502892c117e2ff19837726ec1 [file] [log] [blame]
Geremy Condraedf7b4c2013-03-26 22:19:03 +00001#!/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
Geremy Condra020b5ff2013-03-27 19:33:02 -070068 def generateKeyMap(self, target_build_variant, key_directory):
Geremy Condraedf7b4c2013-03-26 22:19:03 +000069
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
Richard Haines1b46b2f2013-08-08 15:13:29 +010090 tag_path = os.path.expandvars(self.get(tag, option))
91 path = os.path.join(key_directory, tag_path)
Geremy Condraedf7b4c2013-03-26 22:19:03 +000092
93 keyMap[tag] = GenerateKeys(path)
94
95 # Multiple certificates may exist in
96 # the pem file. GenerateKeys supports
97 # this however, the mac_permissions.xml
98 # as well as PMS do not.
99 assert len(keyMap[tag]) == 1
100
101 return keyMap
102
103class ReplaceTags(handler.ContentHandler):
104
105 DEFAULT_TAG = "default"
106 PACKAGE_TAG = "package"
107 POLICY_TAG = "policy"
108 SIGNER_TAG = "signer"
109 SIGNATURE_TAG = "signature"
110
111 TAGS_WITH_CHILDREN = [ DEFAULT_TAG, PACKAGE_TAG, POLICY_TAG, SIGNER_TAG ]
112
113 XML_ENCODING_TAG = '<?xml version="1.0" encoding="iso-8859-1"?>'
114
115 def __init__(self, keyMap, out=sys.stdout):
116
117 handler.ContentHandler.__init__(self)
118 self._keyMap = keyMap
119 self._out = out
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000120 self._out.write(ReplaceTags.XML_ENCODING_TAG)
121 self._out.write("<!-- AUTOGENERATED FILE DO NOT MODIFY -->")
Robert Craig7f2392e2013-03-27 08:35:39 -0400122 self._out.write("<policy>")
123
124 def __del__(self):
125 self._out.write("</policy>")
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000126
127 def startElement(self, tag, attrs):
Robert Craig7f2392e2013-03-27 08:35:39 -0400128 if tag == ReplaceTags.POLICY_TAG:
129 return
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000130
131 self._out.write('<' + tag)
132
133 for (name, value) in attrs.items():
134
135 if name == ReplaceTags.SIGNATURE_TAG and value in self._keyMap:
136 for key in self._keyMap[value].getBase16Keys():
137 logging.info("Replacing " + name + " " + value + " with " + key)
138 self._out.write(' %s="%s"' % (name, saxutils.escape(key)))
139 else:
140 self._out.write(' %s="%s"' % (name, saxutils.escape(value)))
141
142 if tag in ReplaceTags.TAGS_WITH_CHILDREN:
143 self._out.write('>')
144 else:
145 self._out.write('/>')
146
147 def endElement(self, tag):
Robert Craig7f2392e2013-03-27 08:35:39 -0400148 if tag == ReplaceTags.POLICY_TAG:
149 return
150
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000151 if tag in ReplaceTags.TAGS_WITH_CHILDREN:
152 self._out.write('</%s>' % tag)
153
154 def characters(self, content):
155 if not content.isspace():
156 self._out.write(saxutils.escape(content))
157
158 def ignorableWhitespace(self, content):
159 pass
160
161 def processingInstruction(self, target, data):
162 self._out.write('<?%s %s?>' % (target, data))
163
164if __name__ == "__main__":
165
166 # Intentional double space to line up equls signs and opening " for
167 # readability.
Robert Craig7f2392e2013-03-27 08:35:39 -0400168 usage = "usage: %prog [options] CONFIG_FILE MAC_PERMISSIONS_FILE [MAC_PERMISSIONS_FILE...]\n"
169 usage += "This tool allows one to configure an automatic inclusion\n"
170 usage += "of signing keys into the mac_permision.xml file(s) from the\n"
171 usage += "pem files. If mulitple mac_permision.xml files are included\n"
172 usage += "then they are unioned to produce a final version."
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000173
174 version = "%prog " + str(__VERSION)
175
176 parser = OptionParser(usage=usage, version=version)
177
178 parser.add_option("-v", "--verbose",
179 action="store_true", dest="verbose", default=False,
180 help="Print internal operations to stdout")
181
182 parser.add_option("-o", "--output", default="stdout", dest="output_file",
183 metavar="FILE", help="Specify an output file, default is stdout")
184
185 parser.add_option("-c", "--cwd", default=os.getcwd(), dest="root",
186 metavar="DIR", help="Specify a root (CWD) directory to run this from, it" \
187 "chdirs' AFTER loading the config file")
188
189 parser.add_option("-t", "--target-build-variant", default="eng", dest="target_build_variant",
190 help="Specify the TARGET_BUILD_VARIANT, defaults to eng")
191
Geremy Condra020b5ff2013-03-27 19:33:02 -0700192 parser.add_option("-d", "--key-directory", default="", dest="key_directory",
193 help="Specify a parent directory for keys")
194
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000195 (options, args) = parser.parse_args()
196
Robert Craig7f2392e2013-03-27 08:35:39 -0400197 if len(args) < 2:
198 parser.error("Must specify a config file (keys.conf) AND mac_permissions.xml file(s)!")
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000199
200 logging.basicConfig(level=logging.INFO if options.verbose == True else logging.WARN)
201
202 # Read the config file
203 config = ParseConfig()
204 config.read(args[0])
205
206 os.chdir(options.root)
207
208 output_file = sys.stdout if options.output_file == "stdout" else open(options.output_file, "w")
209 logging.info("Setting output file to: " + options.output_file)
210
211 # Generate the key list
Geremy Condra020b5ff2013-03-27 19:33:02 -0700212 key_map = config.generateKeyMap(options.target_build_variant.lower(), options.key_directory)
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000213 logging.info("Generate key map:")
214 for k in key_map:
215 logging.info(k + " : " + str(key_map[k]))
216 # Generate the XML file with markup replaced with keys
217 parser = make_parser()
218 parser.setContentHandler(ReplaceTags(key_map, output_file))
Robert Craig7f2392e2013-03-27 08:35:39 -0400219 for f in args[1:]:
220 parser.parse(f)