xref: /aosp_15_r20/system/sepolicy/tools/insertkeys.py (revision e4a36f4174b17bbab9dc043f4a65dc8d87377290)
1*e4a36f41SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*e4a36f41SAndroid Build Coastguard Worker
3*e4a36f41SAndroid Build Coastguard Workerfrom xml.sax import saxutils, handler, make_parser
4*e4a36f41SAndroid Build Coastguard Workerfrom optparse import OptionParser
5*e4a36f41SAndroid Build Coastguard Workerimport configparser
6*e4a36f41SAndroid Build Coastguard Workerimport logging
7*e4a36f41SAndroid Build Coastguard Workerimport base64
8*e4a36f41SAndroid Build Coastguard Workerimport sys
9*e4a36f41SAndroid Build Coastguard Workerimport os
10*e4a36f41SAndroid Build Coastguard Worker
11*e4a36f41SAndroid Build Coastguard Worker__VERSION = (0, 1)
12*e4a36f41SAndroid Build Coastguard Worker
13*e4a36f41SAndroid Build Coastguard Worker'''
14*e4a36f41SAndroid Build Coastguard WorkerThis tool reads a mac_permissions.xml and replaces keywords in the signature
15*e4a36f41SAndroid Build Coastguard Workerclause with keys provided by pem files.
16*e4a36f41SAndroid Build Coastguard Worker'''
17*e4a36f41SAndroid Build Coastguard Worker
18*e4a36f41SAndroid Build Coastguard Workerclass GenerateKeys(object):
19*e4a36f41SAndroid Build Coastguard Worker    def __init__(self, path):
20*e4a36f41SAndroid Build Coastguard Worker        '''
21*e4a36f41SAndroid Build Coastguard Worker        Generates an object with Base16 and Base64 encoded versions of the keys
22*e4a36f41SAndroid Build Coastguard Worker        found in the supplied pem file argument. PEM files can contain multiple
23*e4a36f41SAndroid Build Coastguard Worker        certs, however this seems to be unused in Android as pkg manager grabs
24*e4a36f41SAndroid Build Coastguard Worker        the first cert in the APK. This will however support multiple certs in
25*e4a36f41SAndroid Build Coastguard Worker        the resulting generation with index[0] being the first cert in the pem
26*e4a36f41SAndroid Build Coastguard Worker        file.
27*e4a36f41SAndroid Build Coastguard Worker        '''
28*e4a36f41SAndroid Build Coastguard Worker
29*e4a36f41SAndroid Build Coastguard Worker        self._base64Key = list()
30*e4a36f41SAndroid Build Coastguard Worker        self._base16Key = list()
31*e4a36f41SAndroid Build Coastguard Worker
32*e4a36f41SAndroid Build Coastguard Worker        if not os.path.isfile(path):
33*e4a36f41SAndroid Build Coastguard Worker            sys.exit("Path " + path + " does not exist or is not a file!")
34*e4a36f41SAndroid Build Coastguard Worker
35*e4a36f41SAndroid Build Coastguard Worker        pkFile = open(path, 'r').readlines()
36*e4a36f41SAndroid Build Coastguard Worker        base64Key = ""
37*e4a36f41SAndroid Build Coastguard Worker        lineNo = 1
38*e4a36f41SAndroid Build Coastguard Worker        certNo = 1
39*e4a36f41SAndroid Build Coastguard Worker        inCert = False
40*e4a36f41SAndroid Build Coastguard Worker        for line in pkFile:
41*e4a36f41SAndroid Build Coastguard Worker            line = line.strip()
42*e4a36f41SAndroid Build Coastguard Worker            # Are we starting the certificate?
43*e4a36f41SAndroid Build Coastguard Worker            if line == "-----BEGIN CERTIFICATE-----":
44*e4a36f41SAndroid Build Coastguard Worker                if inCert:
45*e4a36f41SAndroid Build Coastguard Worker                    sys.exit("Encountered another BEGIN CERTIFICATE without END CERTIFICATE on " +
46*e4a36f41SAndroid Build Coastguard Worker                             "line: " + str(lineNo))
47*e4a36f41SAndroid Build Coastguard Worker
48*e4a36f41SAndroid Build Coastguard Worker                inCert = True
49*e4a36f41SAndroid Build Coastguard Worker
50*e4a36f41SAndroid Build Coastguard Worker            # Are we ending the ceritifcate?
51*e4a36f41SAndroid Build Coastguard Worker            elif line == "-----END CERTIFICATE-----":
52*e4a36f41SAndroid Build Coastguard Worker                if not inCert:
53*e4a36f41SAndroid Build Coastguard Worker                    sys.exit("Encountered END CERTIFICATE before BEGIN CERTIFICATE on line: "
54*e4a36f41SAndroid Build Coastguard Worker                            + str(lineNo))
55*e4a36f41SAndroid Build Coastguard Worker
56*e4a36f41SAndroid Build Coastguard Worker                # If we ended the certificate trip the flag
57*e4a36f41SAndroid Build Coastguard Worker                inCert = False
58*e4a36f41SAndroid Build Coastguard Worker
59*e4a36f41SAndroid Build Coastguard Worker                # Check the input
60*e4a36f41SAndroid Build Coastguard Worker                if len(base64Key) == 0:
61*e4a36f41SAndroid Build Coastguard Worker                    sys.exit("Empty certficate , certificate "+ str(certNo) + " found in file: "
62*e4a36f41SAndroid Build Coastguard Worker                            + path)
63*e4a36f41SAndroid Build Coastguard Worker
64*e4a36f41SAndroid Build Coastguard Worker                # ... and append the certificate to the list
65*e4a36f41SAndroid Build Coastguard Worker                # Base 64 includes uppercase. DO NOT tolower()
66*e4a36f41SAndroid Build Coastguard Worker                self._base64Key.append(base64Key)
67*e4a36f41SAndroid Build Coastguard Worker                try:
68*e4a36f41SAndroid Build Coastguard Worker                    # Pkgmanager and setool see hex strings with lowercase, lets be consistent
69*e4a36f41SAndroid Build Coastguard Worker                    self._base16Key.append(base64.b16encode(base64.b64decode(base64Key)).decode('ascii').lower())
70*e4a36f41SAndroid Build Coastguard Worker                except TypeError:
71*e4a36f41SAndroid Build Coastguard Worker                    sys.exit("Invalid certificate, certificate "+ str(certNo) + " found in file: "
72*e4a36f41SAndroid Build Coastguard Worker                            + path)
73*e4a36f41SAndroid Build Coastguard Worker
74*e4a36f41SAndroid Build Coastguard Worker                # After adding the key, reset the accumulator as pem files may have subsequent keys
75*e4a36f41SAndroid Build Coastguard Worker                base64Key=""
76*e4a36f41SAndroid Build Coastguard Worker
77*e4a36f41SAndroid Build Coastguard Worker                # And increment your cert number
78*e4a36f41SAndroid Build Coastguard Worker                certNo = certNo + 1
79*e4a36f41SAndroid Build Coastguard Worker
80*e4a36f41SAndroid Build Coastguard Worker            # If we haven't started the certificate, then we should not encounter any data
81*e4a36f41SAndroid Build Coastguard Worker            elif not inCert:
82*e4a36f41SAndroid Build Coastguard Worker                if line != "":
83*e4a36f41SAndroid Build Coastguard Worker                    sys.exit("Detected erroneous line \""+ line + "\" on " + str(lineNo)
84*e4a36f41SAndroid Build Coastguard Worker                        + " in pem file: " + path)
85*e4a36f41SAndroid Build Coastguard Worker
86*e4a36f41SAndroid Build Coastguard Worker            # else we have started the certicate and need to append the data
87*e4a36f41SAndroid Build Coastguard Worker            elif inCert:
88*e4a36f41SAndroid Build Coastguard Worker                base64Key += line
89*e4a36f41SAndroid Build Coastguard Worker
90*e4a36f41SAndroid Build Coastguard Worker            else:
91*e4a36f41SAndroid Build Coastguard Worker                # We should never hit this assert, if we do then an unaccounted for state
92*e4a36f41SAndroid Build Coastguard Worker                # was entered that was NOT addressed by the if/elif statements above
93*e4a36f41SAndroid Build Coastguard Worker                assert(False == True)
94*e4a36f41SAndroid Build Coastguard Worker
95*e4a36f41SAndroid Build Coastguard Worker            # The last thing to do before looping up is to increment line number
96*e4a36f41SAndroid Build Coastguard Worker            lineNo = lineNo + 1
97*e4a36f41SAndroid Build Coastguard Worker
98*e4a36f41SAndroid Build Coastguard Worker    def __len__(self):
99*e4a36f41SAndroid Build Coastguard Worker        return len(self._base16Key)
100*e4a36f41SAndroid Build Coastguard Worker
101*e4a36f41SAndroid Build Coastguard Worker    def __str__(self):
102*e4a36f41SAndroid Build Coastguard Worker        return str(self.getBase16Keys())
103*e4a36f41SAndroid Build Coastguard Worker
104*e4a36f41SAndroid Build Coastguard Worker    def getBase16Keys(self):
105*e4a36f41SAndroid Build Coastguard Worker        return self._base16Key
106*e4a36f41SAndroid Build Coastguard Worker
107*e4a36f41SAndroid Build Coastguard Worker    def getBase64Keys(self):
108*e4a36f41SAndroid Build Coastguard Worker        return self._base64Key
109*e4a36f41SAndroid Build Coastguard Worker
110*e4a36f41SAndroid Build Coastguard Workerclass ParseConfig(configparser.ConfigParser):
111*e4a36f41SAndroid Build Coastguard Worker
112*e4a36f41SAndroid Build Coastguard Worker    # This must be lowercase
113*e4a36f41SAndroid Build Coastguard Worker    OPTION_WILDCARD_TAG = "all"
114*e4a36f41SAndroid Build Coastguard Worker
115*e4a36f41SAndroid Build Coastguard Worker    def generateKeyMap(self, target_build_variant, key_directory):
116*e4a36f41SAndroid Build Coastguard Worker
117*e4a36f41SAndroid Build Coastguard Worker        keyMap = dict()
118*e4a36f41SAndroid Build Coastguard Worker
119*e4a36f41SAndroid Build Coastguard Worker        for tag in self.sections():
120*e4a36f41SAndroid Build Coastguard Worker
121*e4a36f41SAndroid Build Coastguard Worker            options = self.options(tag)
122*e4a36f41SAndroid Build Coastguard Worker
123*e4a36f41SAndroid Build Coastguard Worker            for option in options:
124*e4a36f41SAndroid Build Coastguard Worker
125*e4a36f41SAndroid Build Coastguard Worker                # Only generate the key map for debug or release,
126*e4a36f41SAndroid Build Coastguard Worker                # not both!
127*e4a36f41SAndroid Build Coastguard Worker                if option != target_build_variant and \
128*e4a36f41SAndroid Build Coastguard Worker                option != ParseConfig.OPTION_WILDCARD_TAG:
129*e4a36f41SAndroid Build Coastguard Worker                    logging.info("Skipping " + tag + " : " + option +
130*e4a36f41SAndroid Build Coastguard Worker                        " because target build variant is set to " +
131*e4a36f41SAndroid Build Coastguard Worker                        str(target_build_variant))
132*e4a36f41SAndroid Build Coastguard Worker                    continue
133*e4a36f41SAndroid Build Coastguard Worker
134*e4a36f41SAndroid Build Coastguard Worker                if tag in keyMap:
135*e4a36f41SAndroid Build Coastguard Worker                    sys.exit("Duplicate tag detected " + tag)
136*e4a36f41SAndroid Build Coastguard Worker
137*e4a36f41SAndroid Build Coastguard Worker                tag_path = os.path.expandvars(self.get(tag, option))
138*e4a36f41SAndroid Build Coastguard Worker                path = os.path.join(key_directory, tag_path)
139*e4a36f41SAndroid Build Coastguard Worker
140*e4a36f41SAndroid Build Coastguard Worker                keyMap[tag] = GenerateKeys(path)
141*e4a36f41SAndroid Build Coastguard Worker
142*e4a36f41SAndroid Build Coastguard Worker                # Multiple certificates may exist in
143*e4a36f41SAndroid Build Coastguard Worker                # the pem file. GenerateKeys supports
144*e4a36f41SAndroid Build Coastguard Worker                # this however, the mac_permissions.xml
145*e4a36f41SAndroid Build Coastguard Worker                # as well as PMS do not.
146*e4a36f41SAndroid Build Coastguard Worker                assert len(keyMap[tag]) == 1
147*e4a36f41SAndroid Build Coastguard Worker
148*e4a36f41SAndroid Build Coastguard Worker        return keyMap
149*e4a36f41SAndroid Build Coastguard Worker
150*e4a36f41SAndroid Build Coastguard Workerclass ReplaceTags(handler.ContentHandler):
151*e4a36f41SAndroid Build Coastguard Worker
152*e4a36f41SAndroid Build Coastguard Worker    DEFAULT_TAG = "default"
153*e4a36f41SAndroid Build Coastguard Worker    PACKAGE_TAG = "package"
154*e4a36f41SAndroid Build Coastguard Worker    POLICY_TAG = "policy"
155*e4a36f41SAndroid Build Coastguard Worker    SIGNER_TAG = "signer"
156*e4a36f41SAndroid Build Coastguard Worker    SIGNATURE_TAG = "signature"
157*e4a36f41SAndroid Build Coastguard Worker
158*e4a36f41SAndroid Build Coastguard Worker    TAGS_WITH_CHILDREN = [ DEFAULT_TAG, PACKAGE_TAG, POLICY_TAG, SIGNER_TAG ]
159*e4a36f41SAndroid Build Coastguard Worker
160*e4a36f41SAndroid Build Coastguard Worker    XML_ENCODING_TAG = '<?xml version="1.0" encoding="iso-8859-1"?>'
161*e4a36f41SAndroid Build Coastguard Worker
162*e4a36f41SAndroid Build Coastguard Worker    def __init__(self, keyMap, out=sys.stdout):
163*e4a36f41SAndroid Build Coastguard Worker        handler.ContentHandler.__init__(self)
164*e4a36f41SAndroid Build Coastguard Worker        self._keyMap = keyMap
165*e4a36f41SAndroid Build Coastguard Worker        self._out = out
166*e4a36f41SAndroid Build Coastguard Worker
167*e4a36f41SAndroid Build Coastguard Worker    def prologue(self):
168*e4a36f41SAndroid Build Coastguard Worker        self._out.write(ReplaceTags.XML_ENCODING_TAG)
169*e4a36f41SAndroid Build Coastguard Worker        self._out.write("<!-- AUTOGENERATED FILE DO NOT MODIFY -->")
170*e4a36f41SAndroid Build Coastguard Worker        self._out.write("<policy>")
171*e4a36f41SAndroid Build Coastguard Worker
172*e4a36f41SAndroid Build Coastguard Worker    def epilogue(self):
173*e4a36f41SAndroid Build Coastguard Worker        self._out.write("</policy>")
174*e4a36f41SAndroid Build Coastguard Worker
175*e4a36f41SAndroid Build Coastguard Worker    def startElement(self, tag, attrs):
176*e4a36f41SAndroid Build Coastguard Worker        if tag == ReplaceTags.POLICY_TAG:
177*e4a36f41SAndroid Build Coastguard Worker            return
178*e4a36f41SAndroid Build Coastguard Worker
179*e4a36f41SAndroid Build Coastguard Worker        self._out.write('<' + tag)
180*e4a36f41SAndroid Build Coastguard Worker
181*e4a36f41SAndroid Build Coastguard Worker        for (name, value) in attrs.items():
182*e4a36f41SAndroid Build Coastguard Worker
183*e4a36f41SAndroid Build Coastguard Worker            if name == ReplaceTags.SIGNATURE_TAG and value in self._keyMap:
184*e4a36f41SAndroid Build Coastguard Worker                for key in self._keyMap[value].getBase16Keys():
185*e4a36f41SAndroid Build Coastguard Worker                    logging.info("Replacing " + name + " " + value + " with " + key)
186*e4a36f41SAndroid Build Coastguard Worker                    self._out.write(' %s="%s"' % (name, saxutils.escape(key)))
187*e4a36f41SAndroid Build Coastguard Worker            else:
188*e4a36f41SAndroid Build Coastguard Worker                self._out.write(' %s="%s"' % (name, saxutils.escape(value)))
189*e4a36f41SAndroid Build Coastguard Worker
190*e4a36f41SAndroid Build Coastguard Worker        if tag in ReplaceTags.TAGS_WITH_CHILDREN:
191*e4a36f41SAndroid Build Coastguard Worker            self._out.write('>')
192*e4a36f41SAndroid Build Coastguard Worker        else:
193*e4a36f41SAndroid Build Coastguard Worker            self._out.write('/>')
194*e4a36f41SAndroid Build Coastguard Worker
195*e4a36f41SAndroid Build Coastguard Worker    def endElement(self, tag):
196*e4a36f41SAndroid Build Coastguard Worker        if tag == ReplaceTags.POLICY_TAG:
197*e4a36f41SAndroid Build Coastguard Worker            return
198*e4a36f41SAndroid Build Coastguard Worker
199*e4a36f41SAndroid Build Coastguard Worker        if tag in ReplaceTags.TAGS_WITH_CHILDREN:
200*e4a36f41SAndroid Build Coastguard Worker            self._out.write('</%s>' % tag)
201*e4a36f41SAndroid Build Coastguard Worker
202*e4a36f41SAndroid Build Coastguard Worker    def characters(self, content):
203*e4a36f41SAndroid Build Coastguard Worker        if not content.isspace():
204*e4a36f41SAndroid Build Coastguard Worker            self._out.write(saxutils.escape(content))
205*e4a36f41SAndroid Build Coastguard Worker
206*e4a36f41SAndroid Build Coastguard Worker    def ignorableWhitespace(self, content):
207*e4a36f41SAndroid Build Coastguard Worker        pass
208*e4a36f41SAndroid Build Coastguard Worker
209*e4a36f41SAndroid Build Coastguard Worker    def processingInstruction(self, target, data):
210*e4a36f41SAndroid Build Coastguard Worker        self._out.write('<?%s %s?>' % (target, data))
211*e4a36f41SAndroid Build Coastguard Worker
212*e4a36f41SAndroid Build Coastguard Workerif __name__ == "__main__":
213*e4a36f41SAndroid Build Coastguard Worker
214*e4a36f41SAndroid Build Coastguard Worker    usage  = "usage: %prog [options] CONFIG_FILE MAC_PERMISSIONS_FILE [MAC_PERMISSIONS_FILE...]\n"
215*e4a36f41SAndroid Build Coastguard Worker    usage += "This tool allows one to configure an automatic inclusion\n"
216*e4a36f41SAndroid Build Coastguard Worker    usage += "of signing keys into the mac_permision.xml file(s) from the\n"
217*e4a36f41SAndroid Build Coastguard Worker    usage += "pem files. If mulitple mac_permision.xml files are included\n"
218*e4a36f41SAndroid Build Coastguard Worker    usage += "then they are unioned to produce a final version."
219*e4a36f41SAndroid Build Coastguard Worker
220*e4a36f41SAndroid Build Coastguard Worker    version = "%prog " + str(__VERSION)
221*e4a36f41SAndroid Build Coastguard Worker
222*e4a36f41SAndroid Build Coastguard Worker    parser = OptionParser(usage=usage, version=version)
223*e4a36f41SAndroid Build Coastguard Worker
224*e4a36f41SAndroid Build Coastguard Worker    parser.add_option("-v", "--verbose",
225*e4a36f41SAndroid Build Coastguard Worker                      action="store_true", dest="verbose", default=False,
226*e4a36f41SAndroid Build Coastguard Worker                      help="Print internal operations to stdout")
227*e4a36f41SAndroid Build Coastguard Worker
228*e4a36f41SAndroid Build Coastguard Worker    parser.add_option("-o", "--output", default="stdout", dest="output_file",
229*e4a36f41SAndroid Build Coastguard Worker                      metavar="FILE", help="Specify an output file, default is stdout")
230*e4a36f41SAndroid Build Coastguard Worker
231*e4a36f41SAndroid Build Coastguard Worker    parser.add_option("-c", "--cwd", default=os.getcwd(), dest="root",
232*e4a36f41SAndroid Build Coastguard Worker                      metavar="DIR", help="Specify a root (CWD) directory to run this from, it" \
233*e4a36f41SAndroid Build Coastguard Worker                                          "chdirs' AFTER loading the config file")
234*e4a36f41SAndroid Build Coastguard Worker
235*e4a36f41SAndroid Build Coastguard Worker    parser.add_option("-t", "--target-build-variant", default="eng", dest="target_build_variant",
236*e4a36f41SAndroid Build Coastguard Worker                      help="Specify the TARGET_BUILD_VARIANT, defaults to eng")
237*e4a36f41SAndroid Build Coastguard Worker
238*e4a36f41SAndroid Build Coastguard Worker    parser.add_option("-d", "--key-directory", default="", dest="key_directory",
239*e4a36f41SAndroid Build Coastguard Worker                      help="Specify a parent directory for keys")
240*e4a36f41SAndroid Build Coastguard Worker
241*e4a36f41SAndroid Build Coastguard Worker    (options, args) = parser.parse_args()
242*e4a36f41SAndroid Build Coastguard Worker
243*e4a36f41SAndroid Build Coastguard Worker    if len(args) < 2:
244*e4a36f41SAndroid Build Coastguard Worker        parser.error("Must specify a config file (keys.conf) AND mac_permissions.xml file(s)!")
245*e4a36f41SAndroid Build Coastguard Worker
246*e4a36f41SAndroid Build Coastguard Worker    logging.basicConfig(level=logging.INFO if options.verbose == True else logging.WARN)
247*e4a36f41SAndroid Build Coastguard Worker
248*e4a36f41SAndroid Build Coastguard Worker    # Read the config file
249*e4a36f41SAndroid Build Coastguard Worker    config = ParseConfig()
250*e4a36f41SAndroid Build Coastguard Worker    config.read(args[0])
251*e4a36f41SAndroid Build Coastguard Worker
252*e4a36f41SAndroid Build Coastguard Worker    os.chdir(options.root)
253*e4a36f41SAndroid Build Coastguard Worker
254*e4a36f41SAndroid Build Coastguard Worker    output_file = sys.stdout if options.output_file == "stdout" else open(options.output_file, "w")
255*e4a36f41SAndroid Build Coastguard Worker    logging.info("Setting output file to: " + options.output_file)
256*e4a36f41SAndroid Build Coastguard Worker
257*e4a36f41SAndroid Build Coastguard Worker    # Generate the key list
258*e4a36f41SAndroid Build Coastguard Worker    key_map = config.generateKeyMap(options.target_build_variant.lower(), options.key_directory)
259*e4a36f41SAndroid Build Coastguard Worker    logging.info("Generate key map:")
260*e4a36f41SAndroid Build Coastguard Worker    for k in key_map:
261*e4a36f41SAndroid Build Coastguard Worker        logging.info(k + " : " + str(key_map[k]))
262*e4a36f41SAndroid Build Coastguard Worker    # Generate the XML file with markup replaced with keys
263*e4a36f41SAndroid Build Coastguard Worker    parser = make_parser()
264*e4a36f41SAndroid Build Coastguard Worker    handler = ReplaceTags(key_map, output_file)
265*e4a36f41SAndroid Build Coastguard Worker    parser.setContentHandler(handler)
266*e4a36f41SAndroid Build Coastguard Worker    handler.prologue()
267*e4a36f41SAndroid Build Coastguard Worker    for f in args[1:]:
268*e4a36f41SAndroid Build Coastguard Worker        parser.parse(f)
269*e4a36f41SAndroid Build Coastguard Worker    handler.epilogue()
270