blob: e0eee8d72d99d9e02ddb897554e270b23c058262 [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 = ""
William Roberts1ecb4e82013-10-06 18:32:05 -040037 lineNo = 1
38 certNo = 1
Geremy Condraedf7b4c2013-03-26 22:19:03 +000039 inCert = False
40 for line in pkFile:
William Roberts1ecb4e82013-10-06 18:32:05 -040041 line = line.strip()
42 # Are we starting the certificate?
43 if line.startswith("-----BEGIN CERTIFICATE-----"):
44 if inCert:
45 sys.exit("Encountered another BEGIN CERTIFICATE without END CERTIFICATE on " +
46 "line: " + str(lineNo))
Geremy Condraedf7b4c2013-03-26 22:19:03 +000047
William Roberts1ecb4e82013-10-06 18:32:05 -040048 inCert = True
Geremy Condraedf7b4c2013-03-26 22:19:03 +000049
William Roberts1ecb4e82013-10-06 18:32:05 -040050 # Are we ending the ceritifcate?
51 elif line.startswith("-----END CERTIFICATE-----"):
52 if not inCert:
53 sys.exit("Encountered END CERTIFICATE before BEGIN CERTIFICATE on line: "
54 + str(lineNo))
Geremy Condraedf7b4c2013-03-26 22:19:03 +000055
William Roberts1ecb4e82013-10-06 18:32:05 -040056 # If we ended the certificate trip the flag
57 inCert = False
58
59 # Sanity check the input
60 if len(base64Key) == 0:
61 sys.exit("Empty certficate , certificate "+ str(certNo) + " found in file: "
62 + path)
63
64 # ... and append the certificate to the list
65 # Base 64 includes uppercase. DO NOT tolower()
66 self._base64Key.append(base64Key)
67 try:
68 # Pkgmanager and setool see hex strings with lowercase, lets be consistent
69 self._base16Key.append(base64.b16encode(base64.b64decode(base64Key)).lower())
70 except TypeError:
71 sys.exit("Invalid certificate, certificate "+ str(certNo) + " found in file: "
72 + path)
73
74 # After adding the key, reset the accumulator as pem files may have subsequent keys
75 base64Key=""
76
77 # And increment your cert number
78 certNo = certNo + 1
79
80 # If we haven't started the certificate, then we should not encounter any data
81 elif not inCert:
82 sys.exit("Detected erroneous line \""+ line + "\" on " + str(lineNo)
83 + " in pem file: " + path)
84
85 # else we have started the certicate and need to append the data
86 elif inCert:
87 base64Key += line
88
89 else:
90 # We should never hit this assert, if we do then an unaccounted for state
91 # was entered that was NOT addressed by the if/elif statements above
92 assert(False == True)
93
94 # The last thing to do before looping up is to increment line number
95 lineNo = lineNo + 1
Geremy Condraedf7b4c2013-03-26 22:19:03 +000096
97 def __len__(self):
98 return len(self._base16Key)
99
100 def __str__(self):
101 return str(self.getBase16Keys())
102
103 def getBase16Keys(self):
104 return self._base16Key
105
106 def getBase64Keys(self):
107 return self._base64Key
108
109class ParseConfig(ConfigParser.ConfigParser):
110
111 # This must be lowercase
112 OPTION_WILDCARD_TAG = "all"
113
Geremy Condra020b5ff2013-03-27 19:33:02 -0700114 def generateKeyMap(self, target_build_variant, key_directory):
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000115
116 keyMap = dict()
117
118 for tag in self.sections():
119
120 options = self.options(tag)
121
122 for option in options:
123
124 # Only generate the key map for debug or release,
125 # not both!
126 if option != target_build_variant and \
127 option != ParseConfig.OPTION_WILDCARD_TAG:
128 logging.info("Skipping " + tag + " : " + option +
129 " because target build variant is set to " +
130 str(target_build_variant))
131 continue
132
133 if tag in keyMap:
134 sys.exit("Duplicate tag detected " + tag)
135
Richard Haines1b46b2f2013-08-08 15:13:29 +0100136 tag_path = os.path.expandvars(self.get(tag, option))
137 path = os.path.join(key_directory, tag_path)
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000138
139 keyMap[tag] = GenerateKeys(path)
140
141 # Multiple certificates may exist in
142 # the pem file. GenerateKeys supports
143 # this however, the mac_permissions.xml
144 # as well as PMS do not.
145 assert len(keyMap[tag]) == 1
146
147 return keyMap
148
149class ReplaceTags(handler.ContentHandler):
150
151 DEFAULT_TAG = "default"
152 PACKAGE_TAG = "package"
153 POLICY_TAG = "policy"
154 SIGNER_TAG = "signer"
155 SIGNATURE_TAG = "signature"
156
157 TAGS_WITH_CHILDREN = [ DEFAULT_TAG, PACKAGE_TAG, POLICY_TAG, SIGNER_TAG ]
158
159 XML_ENCODING_TAG = '<?xml version="1.0" encoding="iso-8859-1"?>'
160
161 def __init__(self, keyMap, out=sys.stdout):
162
163 handler.ContentHandler.__init__(self)
164 self._keyMap = keyMap
165 self._out = out
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000166 self._out.write(ReplaceTags.XML_ENCODING_TAG)
167 self._out.write("<!-- AUTOGENERATED FILE DO NOT MODIFY -->")
Robert Craig7f2392e2013-03-27 08:35:39 -0400168 self._out.write("<policy>")
169
170 def __del__(self):
171 self._out.write("</policy>")
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000172
173 def startElement(self, tag, attrs):
Robert Craig7f2392e2013-03-27 08:35:39 -0400174 if tag == ReplaceTags.POLICY_TAG:
175 return
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000176
177 self._out.write('<' + tag)
178
179 for (name, value) in attrs.items():
180
181 if name == ReplaceTags.SIGNATURE_TAG and value in self._keyMap:
182 for key in self._keyMap[value].getBase16Keys():
183 logging.info("Replacing " + name + " " + value + " with " + key)
184 self._out.write(' %s="%s"' % (name, saxutils.escape(key)))
185 else:
186 self._out.write(' %s="%s"' % (name, saxutils.escape(value)))
187
188 if tag in ReplaceTags.TAGS_WITH_CHILDREN:
189 self._out.write('>')
190 else:
191 self._out.write('/>')
192
193 def endElement(self, tag):
Robert Craig7f2392e2013-03-27 08:35:39 -0400194 if tag == ReplaceTags.POLICY_TAG:
195 return
196
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000197 if tag in ReplaceTags.TAGS_WITH_CHILDREN:
198 self._out.write('</%s>' % tag)
199
200 def characters(self, content):
201 if not content.isspace():
202 self._out.write(saxutils.escape(content))
203
204 def ignorableWhitespace(self, content):
205 pass
206
207 def processingInstruction(self, target, data):
208 self._out.write('<?%s %s?>' % (target, data))
209
210if __name__ == "__main__":
211
212 # Intentional double space to line up equls signs and opening " for
213 # readability.
Robert Craig7f2392e2013-03-27 08:35:39 -0400214 usage = "usage: %prog [options] CONFIG_FILE MAC_PERMISSIONS_FILE [MAC_PERMISSIONS_FILE...]\n"
215 usage += "This tool allows one to configure an automatic inclusion\n"
216 usage += "of signing keys into the mac_permision.xml file(s) from the\n"
217 usage += "pem files. If mulitple mac_permision.xml files are included\n"
218 usage += "then they are unioned to produce a final version."
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000219
220 version = "%prog " + str(__VERSION)
221
222 parser = OptionParser(usage=usage, version=version)
223
224 parser.add_option("-v", "--verbose",
225 action="store_true", dest="verbose", default=False,
226 help="Print internal operations to stdout")
227
228 parser.add_option("-o", "--output", default="stdout", dest="output_file",
229 metavar="FILE", help="Specify an output file, default is stdout")
230
231 parser.add_option("-c", "--cwd", default=os.getcwd(), dest="root",
232 metavar="DIR", help="Specify a root (CWD) directory to run this from, it" \
233 "chdirs' AFTER loading the config file")
234
235 parser.add_option("-t", "--target-build-variant", default="eng", dest="target_build_variant",
236 help="Specify the TARGET_BUILD_VARIANT, defaults to eng")
237
Geremy Condra020b5ff2013-03-27 19:33:02 -0700238 parser.add_option("-d", "--key-directory", default="", dest="key_directory",
239 help="Specify a parent directory for keys")
240
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000241 (options, args) = parser.parse_args()
242
Robert Craig7f2392e2013-03-27 08:35:39 -0400243 if len(args) < 2:
244 parser.error("Must specify a config file (keys.conf) AND mac_permissions.xml file(s)!")
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000245
246 logging.basicConfig(level=logging.INFO if options.verbose == True else logging.WARN)
247
248 # Read the config file
249 config = ParseConfig()
250 config.read(args[0])
251
252 os.chdir(options.root)
253
254 output_file = sys.stdout if options.output_file == "stdout" else open(options.output_file, "w")
255 logging.info("Setting output file to: " + options.output_file)
256
257 # Generate the key list
Geremy Condra020b5ff2013-03-27 19:33:02 -0700258 key_map = config.generateKeyMap(options.target_build_variant.lower(), options.key_directory)
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000259 logging.info("Generate key map:")
260 for k in key_map:
261 logging.info(k + " : " + str(key_map[k]))
262 # Generate the XML file with markup replaced with keys
263 parser = make_parser()
264 parser.setContentHandler(ReplaceTags(key_map, output_file))
Robert Craig7f2392e2013-03-27 08:35:39 -0400265 for f in args[1:]:
266 parser.parse(f)