blob: 24f0dacb69b43c3fefcc71324e496d1c8e5ed05e [file] [log] [blame]
Thiébaud Weksteen98707252021-12-03 13:38:04 +11001#!/usr/bin/env python3
Geremy Condraedf7b4c2013-03-26 22:19:03 +00002
3from xml.sax import saxutils, handler, make_parser
4from optparse import OptionParser
Thiébaud Weksteen98707252021-12-03 13:38:04 +11005import configparser
Geremy Condraedf7b4c2013-03-26 22:19:03 +00006import 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
Thiébaud Weksteen98707252021-12-03 13:38:04 +110035 pkFile = open(path, 'r').readlines()
Geremy Condraedf7b4c2013-03-26 22:19:03 +000036 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?
William Roberts14138332013-10-14 15:51:48 -070043 if line == "-----BEGIN CERTIFICATE-----":
William Roberts1ecb4e82013-10-06 18:32:05 -040044 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?
William Roberts14138332013-10-14 15:51:48 -070051 elif line == "-----END CERTIFICATE-----":
William Roberts1ecb4e82013-10-06 18:32:05 -040052 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
Joel Galensonb0d74a12020-07-27 09:30:34 -070059 # Check the input
William Roberts1ecb4e82013-10-06 18:32:05 -040060 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
Thiébaud Weksteen98707252021-12-03 13:38:04 +110069 self._base16Key.append(base64.b16encode(base64.b64decode(base64Key)).decode('ascii').lower())
William Roberts1ecb4e82013-10-06 18:32:05 -040070 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:
Thiébaud Weksteen98707252021-12-03 13:38:04 +110082 if line != "":
Mike Palmiotto070c01f2013-10-10 16:37:03 -040083 sys.exit("Detected erroneous line \""+ line + "\" on " + str(lineNo)
William Roberts1ecb4e82013-10-06 18:32:05 -040084 + " in pem file: " + path)
85
86 # else we have started the certicate and need to append the data
87 elif inCert:
88 base64Key += line
89
90 else:
91 # We should never hit this assert, if we do then an unaccounted for state
92 # was entered that was NOT addressed by the if/elif statements above
93 assert(False == True)
94
95 # The last thing to do before looping up is to increment line number
96 lineNo = lineNo + 1
Geremy Condraedf7b4c2013-03-26 22:19:03 +000097
98 def __len__(self):
99 return len(self._base16Key)
100
101 def __str__(self):
102 return str(self.getBase16Keys())
103
104 def getBase16Keys(self):
105 return self._base16Key
106
107 def getBase64Keys(self):
108 return self._base64Key
109
Thiébaud Weksteen98707252021-12-03 13:38:04 +1100110class ParseConfig(configparser.ConfigParser):
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000111
112 # This must be lowercase
113 OPTION_WILDCARD_TAG = "all"
114
Geremy Condra020b5ff2013-03-27 19:33:02 -0700115 def generateKeyMap(self, target_build_variant, key_directory):
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000116
117 keyMap = dict()
118
119 for tag in self.sections():
120
121 options = self.options(tag)
122
123 for option in options:
124
125 # Only generate the key map for debug or release,
126 # not both!
127 if option != target_build_variant and \
128 option != ParseConfig.OPTION_WILDCARD_TAG:
129 logging.info("Skipping " + tag + " : " + option +
130 " because target build variant is set to " +
131 str(target_build_variant))
132 continue
133
134 if tag in keyMap:
135 sys.exit("Duplicate tag detected " + tag)
136
Richard Haines1b46b2f2013-08-08 15:13:29 +0100137 tag_path = os.path.expandvars(self.get(tag, option))
138 path = os.path.join(key_directory, tag_path)
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000139
140 keyMap[tag] = GenerateKeys(path)
141
142 # Multiple certificates may exist in
143 # the pem file. GenerateKeys supports
144 # this however, the mac_permissions.xml
145 # as well as PMS do not.
146 assert len(keyMap[tag]) == 1
147
148 return keyMap
149
150class ReplaceTags(handler.ContentHandler):
151
152 DEFAULT_TAG = "default"
153 PACKAGE_TAG = "package"
154 POLICY_TAG = "policy"
155 SIGNER_TAG = "signer"
156 SIGNATURE_TAG = "signature"
157
158 TAGS_WITH_CHILDREN = [ DEFAULT_TAG, PACKAGE_TAG, POLICY_TAG, SIGNER_TAG ]
159
160 XML_ENCODING_TAG = '<?xml version="1.0" encoding="iso-8859-1"?>'
161
162 def __init__(self, keyMap, out=sys.stdout):
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000163 handler.ContentHandler.__init__(self)
164 self._keyMap = keyMap
165 self._out = out
Thiébaud Weksteen98707252021-12-03 13:38:04 +1100166
167 def prologue(self):
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000168 self._out.write(ReplaceTags.XML_ENCODING_TAG)
169 self._out.write("<!-- AUTOGENERATED FILE DO NOT MODIFY -->")
Robert Craig7f2392e2013-03-27 08:35:39 -0400170 self._out.write("<policy>")
171
Thiébaud Weksteen98707252021-12-03 13:38:04 +1100172 def epilogue(self):
Robert Craig7f2392e2013-03-27 08:35:39 -0400173 self._out.write("</policy>")
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000174
175 def startElement(self, tag, attrs):
Robert Craig7f2392e2013-03-27 08:35:39 -0400176 if tag == ReplaceTags.POLICY_TAG:
177 return
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000178
179 self._out.write('<' + tag)
180
181 for (name, value) in attrs.items():
182
183 if name == ReplaceTags.SIGNATURE_TAG and value in self._keyMap:
184 for key in self._keyMap[value].getBase16Keys():
185 logging.info("Replacing " + name + " " + value + " with " + key)
186 self._out.write(' %s="%s"' % (name, saxutils.escape(key)))
187 else:
188 self._out.write(' %s="%s"' % (name, saxutils.escape(value)))
189
190 if tag in ReplaceTags.TAGS_WITH_CHILDREN:
191 self._out.write('>')
192 else:
193 self._out.write('/>')
194
195 def endElement(self, tag):
Robert Craig7f2392e2013-03-27 08:35:39 -0400196 if tag == ReplaceTags.POLICY_TAG:
197 return
198
Geremy Condraedf7b4c2013-03-26 22:19:03 +0000199 if tag in ReplaceTags.TAGS_WITH_CHILDREN:
200 self._out.write('</%s>' % tag)
201
202 def characters(self, content):
203 if not content.isspace():
204 self._out.write(saxutils.escape(content))
205
206 def ignorableWhitespace(self, content):
207 pass
208
209 def processingInstruction(self, target, data):
210 self._out.write('<?%s %s?>' % (target, data))
211
212if __name__ == "__main__":
213
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()
Thiébaud Weksteen98707252021-12-03 13:38:04 +1100264 handler = ReplaceTags(key_map, output_file)
265 parser.setContentHandler(handler)
266 handler.prologue()
Robert Craig7f2392e2013-03-27 08:35:39 -0400267 for f in args[1:]:
268 parser.parse(f)
Thiébaud Weksteen98707252021-12-03 13:38:04 +1100269 handler.epilogue()