• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/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, key_directory):
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 = os.path.join(key_directory, 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        self._out.write(ReplaceTags.XML_ENCODING_TAG)
120        self._out.write("<!-- AUTOGENERATED FILE DO NOT MODIFY -->")
121        self._out.write("<policy>")
122
123    def __del__(self):
124        self._out.write("</policy>")
125
126    def startElement(self, tag, attrs):
127        if tag == ReplaceTags.POLICY_TAG:
128            return
129
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):
147        if tag == ReplaceTags.POLICY_TAG:
148            return
149
150        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.
167    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."
172
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
191    parser.add_option("-d", "--key-directory", default="", dest="key_directory",
192                      help="Specify a parent directory for keys")
193
194    (options, args) = parser.parse_args()
195
196    if len(args) < 2:
197        parser.error("Must specify a config file (keys.conf) AND mac_permissions.xml file(s)!")
198
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
211    key_map = config.generateKeyMap(options.target_build_variant.lower(), options.key_directory)
212    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))
218    for f in args[1:]:
219        parser.parse(f)
220