blob: 509c43fa5c32fa2a599eb5658ae9707d3d1c2943 [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
68 def generateKeyMap(self, target_build_variant):
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 = 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
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000119 self._out.write(ReplaceTags.XML_ENCODING_TAG)
120 self._out.write("<!-- AUTOGENERATED FILE DO NOT MODIFY -->")
Robert Craig7f2392e2013-03-27 08:35:39 -0400121 self._out.write("<policy>")
122
123 def __del__(self):
124 self._out.write("</policy>")
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000125
126 def startElement(self, tag, attrs):
Robert Craig7f2392e2013-03-27 08:35:39 -0400127 if tag == ReplaceTags.POLICY_TAG:
128 return
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000129
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):
Robert Craig7f2392e2013-03-27 08:35:39 -0400147 if tag == ReplaceTags.POLICY_TAG:
148 return
149
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000150 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.
Robert Craig7f2392e2013-03-27 08:35:39 -0400167 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."
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000172
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
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000191 (options, args) = parser.parse_args()
192
Robert Craig7f2392e2013-03-27 08:35:39 -0400193 if len(args) < 2:
194 parser.error("Must specify a config file (keys.conf) AND mac_permissions.xml file(s)!")
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000195
196 logging.basicConfig(level=logging.INFO if options.verbose == True else logging.WARN)
197
198 # Read the config file
199 config = ParseConfig()
200 config.read(args[0])
201
202 os.chdir(options.root)
203
204 output_file = sys.stdout if options.output_file == "stdout" else open(options.output_file, "w")
205 logging.info("Setting output file to: " + options.output_file)
206
207 # Generate the key list
208 key_map = config.generateKeyMap(options.target_build_variant.lower())
209 logging.info("Generate key map:")
210 for k in key_map:
211 logging.info(k + " : " + str(key_map[k]))
212 # Generate the XML file with markup replaced with keys
213 parser = make_parser()
214 parser.setContentHandler(ReplaceTags(key_map, output_file))
Robert Craig7f2392e2013-03-27 08:35:39 -0400215 for f in args[1:]:
216 parser.parse(f)