[Replicant] [vendor_replicant-scripts] [PATCH v5 4/4] Add new script to extract certificates from /data/system/packages.xml

Denis 'GNUtoo' Carikli GNUtoo at cyberdimension.org
Fri Oct 9 16:55:58 UTC 2020


Signed-off-by: Denis 'GNUtoo' Carikli <GNUtoo at cyberdimension.org>
---
 .../gen_key_migration_script/extract_certs.py | 150 ++++++++++++++++++
 1 file changed, 150 insertions(+)
 create mode 100755 images/gen_key_migration_script/extract_certs.py

diff --git a/images/gen_key_migration_script/extract_certs.py b/images/gen_key_migration_script/extract_certs.py
new file mode 100755
index 0000000..a08d3e1
--- /dev/null
+++ b/images/gen_key_migration_script/extract_certs.py
@@ -0,0 +1,150 @@
+#!/usr/bin/env python3
+# Copyright (C) 2020 Denis 'GNUtoo' Carikli <GNUtoo at cyberdimension.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+import os
+import re
+import sh
+import sys
+
+# Etree isn't subject to any data leak vulnerabilities:
+# +---------------------------+------------+-----------------------------------+
+# | Issue                     | Etree      | Issue description                 |
+# +---------------------------+------------+-----------------------------------+
+# | billion laughs            | Vulnerable | DDOS by huge CPU and memory usage |
+# +---------------------------+------------+-----------------------------------+
+# | quadratic blowup          | Vulnerable | DDOS by huge CPU and memory usage |
+# +---------------------------+------------+-----------------------------------+
+# | external entity expansion | Safe (1)   | Data leak                         |
+# +---------------------------+------------+-----------------------------------+
+# | DTD retrieval             | Safe       | Data leak                         |
+# +---------------------------+------------+-----------------------------------+
+# | decompression bomb        | Safe       | DDOS by huge amount of CPU        |
+# +---------------------------+------------+-----------------------------------+
+# (1) xml.etree.ElementTree doesn’t expand external entities and raises a
+#     ParserError when an entity occurs.
+#
+# Other XML parsers like sax, minidom, pulldom, xmlrpc have similar security
+# properties: they are not vulnerable (anymore) to data leaks but they are still
+# vulnerable to DDOS attacks.
+# Reference: https://docs.python.org/3.8/library/xml.html#xml-vulnerabilities
+import xml.etree.ElementTree
+
+import jinja2
+
+def usage(progname):
+    print ('Usage:')
+    print ('\t{} <path/to/packages.xml> <path/to/out/dir/>'.format(progname))
+    sys.exit(1)
+
+def find_keys(results, root):
+    # Error if there is no keyset-settings or keys
+    keyset_settings = root.findall('keyset-settings')
+    assert (len(keyset_settings) == 1)
+
+    keys = keyset_settings[0].findall('keys')
+    assert (len(keys) == 1)
+
+    public_key_entries = keys[0].findall('public-key')
+    assert (len(public_key_entries) >= 1)
+
+    for public_key in public_key_entries:
+        identifier = public_key.get('identifier')
+        value = public_key.get('value')
+        results[identifier] = {
+            'PEM-pubkey' : value,
+            'packages' : [],
+        }
+
+    return results
+
+def parse_packages_xml_file(results, path):
+    xml_file = open(path, 'r')
+    tree = xml.etree.ElementTree.parse(xml_file)
+    root = tree.getroot()
+
+    find_keys(results, root)
+
+    # elm.iter(<tag>) iterates over the elm element and all elements below it
+    for package in root.iter('package'):
+        package_name = package.get('name')
+        proper_signing_keyset = package.findall('proper-signing-keyset')
+        assert(len(proper_signing_keyset) == 1)
+
+        identifier = proper_signing_keyset[0].get('identifier')
+
+        results[identifier]['packages'].append(package_name)
+
+        for cert in package.iter('cert'):
+            if 'key' in cert.keys():
+                results[identifier]['DER-cert'] = cert.get('key')
+
+    xml_file.close()
+
+def write_pem_cert(der_cert, path):
+    openssl = sh.Command('openssl')
+    data = bytes.fromhex(der_cert)
+
+    output = openssl('x509',
+                     '-inform', 'DER',
+                     '-outform', 'PEM',
+                     '-out', path, _in=data)
+
+def get_filename(id, data):
+    if 'android' in packages:
+        return 'platform.x509.pem'
+    elif 'com.cyanogenmod.trebuchet' in packages:
+        return 'releasekey.x509.pem'
+    elif 'com.android.contacts' in packages:
+        return 'shared.x509.pem'
+    elif 'com.android.providers.media' in packages:
+        return 'media.x509.pem'
+    elif len (data['packages']) == 1:
+        return str(packages[0]) + '.x509.pem'
+    else:
+        return str(id) + '.x509.pem'
+
+if __name__ == '__main__':
+    # The packages.xml file has the certificate keys encoded twice:
+    # - Once in PEM format in the value= in:
+    #   <keyset-settings version="1">
+    #       <keys>
+    #           <public-key identifier="1" value="..." />
+    # - Once in DER format in key= in:
+    #   <package name="..." [...]>
+    #       <sigs [...]>
+    #           <cert [...] key="..." />
+    if len(sys.argv) != 3:
+           usage(sys.argv[0])
+
+    packages_xml_path = sys.argv[1]
+    out_dir_path = sys.argv[2]
+
+    results = {}
+    parse_packages_xml_file(results, packages_xml_path)
+
+    for id, data in results.items():
+        packages = data['packages']
+
+        base_path = out_dir_path + os.sep + get_filename(id, data)
+
+        if len(packages) > 1:
+            file = open(base_path + '.txt', 'w')
+            file.write('Packages:' + os.linesep)
+            for package in packages:
+                file.write("- " + package + os.linesep)
+            file.close()
+
+        write_pem_cert(data['DER-cert'], base_path)
-- 
2.28.0



More information about the Replicant mailing list