blob: e4eeb43f8dd28e7c7abeb2105f334088795dd68c [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
119
120 def startDocument(self):
121 self._out.write(ReplaceTags.XML_ENCODING_TAG)
122 self._out.write("<!-- AUTOGENERATED FILE DO NOT MODIFY -->")
123
124 def startElement(self, tag, attrs):
125
126 self._out.write('<' + tag)
127
128 for (name, value) in attrs.items():
129
130 if name == ReplaceTags.SIGNATURE_TAG and value in self._keyMap:
131 for key in self._keyMap[value].getBase16Keys():
132 logging.info("Replacing " + name + " " + value + " with " + key)
133 self._out.write(' %s="%s"' % (name, saxutils.escape(key)))
134 else:
135 self._out.write(' %s="%s"' % (name, saxutils.escape(value)))
136
137 if tag in ReplaceTags.TAGS_WITH_CHILDREN:
138 self._out.write('>')
139 else:
140 self._out.write('/>')
141
142 def endElement(self, tag):
143 if tag in ReplaceTags.TAGS_WITH_CHILDREN:
144 self._out.write('</%s>' % tag)
145
146 def characters(self, content):
147 if not content.isspace():
148 self._out.write(saxutils.escape(content))
149
150 def ignorableWhitespace(self, content):
151 pass
152
153 def processingInstruction(self, target, data):
154 self._out.write('<?%s %s?>' % (target, data))
155
156if __name__ == "__main__":
157
158 # Intentional double space to line up equls signs and opening " for
159 # readability.
160 usage = "usage: %prog [options] CONFIG_FILE MAC_PERMISSIONS_FILE\n"
161 usage += "This tool allows one to configure an automatic inclusion "
162 usage += "of signing keys into the mac_permision.xml file from the "
163 usage += "pem files."
164
165 version = "%prog " + str(__VERSION)
166
167 parser = OptionParser(usage=usage, version=version)
168
169 parser.add_option("-v", "--verbose",
170 action="store_true", dest="verbose", default=False,
171 help="Print internal operations to stdout")
172
173 parser.add_option("-o", "--output", default="stdout", dest="output_file",
174 metavar="FILE", help="Specify an output file, default is stdout")
175
176 parser.add_option("-c", "--cwd", default=os.getcwd(), dest="root",
177 metavar="DIR", help="Specify a root (CWD) directory to run this from, it" \
178 "chdirs' AFTER loading the config file")
179
180 parser.add_option("-t", "--target-build-variant", default="eng", dest="target_build_variant",
181 help="Specify the TARGET_BUILD_VARIANT, defaults to eng")
182
183
184 (options, args) = parser.parse_args()
185
186 if len(args) != 2:
187 parser.error("Must specify a config file (keys.conf) AND mac_permissions.xml file!")
188
189 logging.basicConfig(level=logging.INFO if options.verbose == True else logging.WARN)
190
191 # Read the config file
192 config = ParseConfig()
193 config.read(args[0])
194
195 os.chdir(options.root)
196
197 output_file = sys.stdout if options.output_file == "stdout" else open(options.output_file, "w")
198 logging.info("Setting output file to: " + options.output_file)
199
200 # Generate the key list
201 key_map = config.generateKeyMap(options.target_build_variant.lower())
202 logging.info("Generate key map:")
203 for k in key_map:
204 logging.info(k + " : " + str(key_map[k]))
205 # Generate the XML file with markup replaced with keys
206 parser = make_parser()
207 parser.setContentHandler(ReplaceTags(key_map, output_file))
208 parser.parse(args[1])