blob: cccb92f1ded7b35fadaaf6a1a1962864ef42316a [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
Geremy Condra020b5ff2013-03-27 19:33:02 -070090 path = os.path.join(key_directory, self.get(tag, option))
Geremy Condraedf7b4c2013-03-26 22:19:03 +000091
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 Condra020b5ff2013-03-27 19:33:02 -0700191 parser.add_option("-d", "--key-directory", default="", dest="key_directory",
192 help="Specify a parent directory for keys")
193
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000194 (options, args) = parser.parse_args()
195
Robert Craig7f2392e2013-03-27 08:35:39 -0400196 if len(args) < 2:
197 parser.error("Must specify a config file (keys.conf) AND mac_permissions.xml file(s)!")
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000198
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
Geremy Condra020b5ff2013-03-27 19:33:02 -0700211 key_map = config.generateKeyMap(options.target_build_variant.lower(), options.key_directory)
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000212 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))
Robert Craig7f2392e2013-03-27 08:35:39 -0400218 for f in args[1:]:
219 parser.parse(f)