summaryrefslogtreecommitdiffstats
path: root/testing/mozbase/mozproxy/mozproxy/backends/mitm/android.py
diff options
context:
space:
mode:
Diffstat (limited to 'testing/mozbase/mozproxy/mozproxy/backends/mitm/android.py')
-rw-r--r--testing/mozbase/mozproxy/mozproxy/backends/mitm/android.py245
1 files changed, 245 insertions, 0 deletions
diff --git a/testing/mozbase/mozproxy/mozproxy/backends/mitm/android.py b/testing/mozbase/mozproxy/mozproxy/backends/mitm/android.py
new file mode 100644
index 0000000000..d13ba479f6
--- /dev/null
+++ b/testing/mozbase/mozproxy/mozproxy/backends/mitm/android.py
@@ -0,0 +1,245 @@
+"""Functions to download, install, setup, and use the mitmproxy playback tool"""
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+import glob
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+from subprocess import PIPE
+
+import mozinfo
+
+from mozproxy.backends.mitm.mitm import Mitmproxy
+from mozproxy.utils import LOG, download_file_from_url, tooltool_download
+
+# path for mitmproxy certificate, generated auto after mitmdump is started
+# on local machine it is 'HOME', however it is different on production machines
+try:
+ DEFAULT_CERT_PATH = os.path.join(
+ os.getenv("HOME"), ".mitmproxy", "mitmproxy-ca-cert.cer"
+ )
+except Exception:
+ DEFAULT_CERT_PATH = os.path.join(
+ os.getenv("HOMEDRIVE"),
+ os.getenv("HOMEPATH"),
+ ".mitmproxy",
+ "mitmproxy-ca-cert.cer",
+ )
+
+# On Windows, deal with mozilla-build having forward slashes in $HOME:
+if os.name == "nt" and "/" in DEFAULT_CERT_PATH:
+ DEFAULT_CERT_PATH = DEFAULT_CERT_PATH.replace("/", "\\")
+
+FIREFOX_ANDROID_APPS = [
+ "geckoview",
+ "refbrow",
+ "fenix",
+]
+
+
+class MitmproxyAndroid(Mitmproxy):
+ def setup(self):
+ self.download_and_install_host_utils()
+ self.install_mitmproxy_cert()
+
+ def download_and_install_host_utils(self):
+ """
+ If running locally:
+ 1. Will use the `certutil` tool from the local Firefox desktop build
+
+ If running in production:
+ 1. Get the tooltools manifest file for downloading hostutils (contains certutil)
+ 2. Get the `certutil` tool by downloading hostutils using the tooltool manifest
+ """
+ if self.config["run_local"]:
+ # when running locally, it is found in the Firefox desktop build (..obj../dist/bin)
+ self.certutil_path = os.path.join(os.environ["MOZ_HOST_BIN"], "certutil")
+ if not (
+ os.path.isfile(self.certutil_path)
+ and os.access(self.certutil_path, os.X_OK)
+ ):
+ raise Exception(
+ "Abort: unable to execute certutil: {}".format(self.certutil_path)
+ )
+ self.certutil_path = os.environ["MOZ_HOST_BIN"]
+ os.environ["LD_LIBRARY_PATH"] = self.certutil_path
+ else:
+ # must download certutil inside hostutils via tooltool; use this manifest:
+ # mozilla-central/testing/config/tooltool-manifests/linux64/hostutils.manifest
+ # after it will be found here inside the worker/bitbar container:
+ # /builds/worker/workspace/build/hostutils/host-utils-66.0a1.en-US.linux-x86_64
+ LOG.info("downloading certutil binary (hostutils)")
+
+ # get path to the hostutils tooltool manifest; was set earlier in
+ # mozharness/configs/raptor/android_hw_config.py, to the path i.e.
+ # mozilla-central/testing/config/tooltool-manifests/linux64/hostutils.manifest
+ # the bitbar container is always linux64
+ if os.environ.get("GECKO_HEAD_REPOSITORY", None) is None:
+ raise Exception("Abort: unable to get GECKO_HEAD_REPOSITORY")
+
+ if os.environ.get("GECKO_HEAD_REV", None) is None:
+ raise Exception("Abort: unable to get GECKO_HEAD_REV")
+
+ if os.environ.get("HOSTUTILS_MANIFEST_PATH", None) is not None:
+ manifest_url = os.path.join(
+ os.environ["GECKO_HEAD_REPOSITORY"],
+ "raw-file",
+ os.environ["GECKO_HEAD_REV"],
+ os.environ["HOSTUTILS_MANIFEST_PATH"],
+ )
+ else:
+ raise Exception("Abort: unable to get HOSTUTILS_MANIFEST_PATH!")
+
+ # first need to download the hostutils tooltool manifest file itself
+ _dest = os.path.join(self.mozproxy_dir, "hostutils.manifest")
+ have_manifest = download_file_from_url(manifest_url, _dest)
+ if not have_manifest:
+ raise Exception("failed to download the hostutils tooltool manifest")
+
+ # now use the manifest to download hostutils so we can get certutil
+ tooltool_download(_dest, self.config["run_local"], self.mozproxy_dir)
+
+ # the production bitbar container host is always linux
+ self.certutil_path = glob.glob(
+ os.path.join(self.mozproxy_dir, "host-utils*[!z|checksum]")
+ )[0]
+
+ # must add hostutils/certutil to the path
+ os.environ["LD_LIBRARY_PATH"] = self.certutil_path
+
+ bin_suffix = mozinfo.info.get("bin_suffix", "")
+ self.certutil_path = os.path.join(self.certutil_path, "certutil" + bin_suffix)
+ if os.path.isfile(self.certutil_path):
+ LOG.info("certutil is found at: %s" % self.certutil_path)
+ else:
+ raise Exception("unable to find certutil at %s" % self.certutil_path)
+
+ def install_mitmproxy_cert(self):
+ """Install the CA certificate generated by mitmproxy, into geckoview android
+
+ 1. Create an NSS certificate database in the geckoview browser profile dir, only
+ if it doesn't already exist. Use this certutil command:
+ `certutil -N -d sql:<path to profile> --empty-password`
+ 2. Import the mitmproxy certificate into the database, i.e.:
+ `certutil -A -d sql:<path to profile> -n "some nickname" -t TC,, -a -i <path to CA.pem>`
+ """
+ # If the app isn't in FIREFOX_ANDROID_APPS then we have to create the tempdir
+ # because we don't have it available in the local_profile_dir
+ if self.config["app"] in FIREFOX_ANDROID_APPS:
+ tempdir = self.config["local_profile_dir"]
+ else:
+ tempdir = tempfile.mkdtemp()
+ cert_db_location = "sql:%s/" % tempdir
+
+ if not self.cert_db_exists(cert_db_location):
+ self.create_cert_db(cert_db_location)
+
+ # DEFAULT_CERT_PATH has local path and name of mitmproxy cert i.e.
+ # /home/cltbld/.mitmproxy/mitmproxy-ca-cert.cer
+ self.import_certificate_in_cert_db(cert_db_location, DEFAULT_CERT_PATH)
+
+ # cannot continue if failed to add CA cert to Firefox, need to check
+ if not self.is_mitmproxy_cert_installed(cert_db_location):
+ LOG.error("Aborting: failed to install mitmproxy CA cert into Firefox")
+ self.stop_mitmproxy_playback()
+ sys.exit()
+ if self.config["app"] not in FIREFOX_ANDROID_APPS:
+ try:
+ shutil.rmtree(tempdir)
+ except Exception:
+ LOG.warning("unable to remove directory: %s" % tempdir)
+
+ def import_certificate_in_cert_db(self, cert_db_location, local_cert_path):
+ # import mitmproxy cert into the db
+ args = [
+ "-A",
+ "-d",
+ cert_db_location,
+ "-n",
+ "mitmproxy-cert",
+ "-t",
+ "TC,,",
+ "-a",
+ "-i",
+ local_cert_path,
+ ]
+ LOG.info("importing mitmproxy cert into db using command")
+ self.certutil(args)
+
+ def create_cert_db(self, cert_db_location):
+ # create cert db if it doesn't already exist; it may exist already
+ # if a previous pageload test ran in the same test suite
+ args = ["-d", cert_db_location, "-N", "--empty-password"]
+
+ LOG.info("creating nss cert database")
+
+ self.certutil(args)
+
+ if not self.cert_db_exists(cert_db_location):
+ raise Exception("nss cert db creation command failed. Cert db not created.")
+
+ def cert_db_exists(self, cert_db_location):
+ # check if the nss ca cert db already exists in the device profile
+ LOG.info(
+ "checking if the nss cert db already exists in the android browser profile"
+ )
+
+ args = ["-d", cert_db_location, "-L"]
+ cert_db_exists = self.certutil(args, raise_exception=False)
+
+ if cert_db_exists:
+ LOG.info("the nss cert db exists")
+ return True
+ else:
+ LOG.info("nss cert db doesn't exist yet.")
+ return False
+
+ def is_mitmproxy_cert_installed(self, cert_db_location):
+ """Verify mitmxproy CA cert was added to Firefox on android"""
+ LOG.info("verifying that the mitmproxy ca cert is installed on android")
+
+ # list the certifcates that are in the nss cert db (inside the browser profile dir)
+ LOG.info(
+ "getting the list of certs in the nss cert db in the android browser profile"
+ )
+ args = ["-d", cert_db_location, "-L"]
+
+ cmd_output = self.certutil(args)
+
+ if "mitmproxy-cert" in cmd_output.decode("utf-8"):
+ LOG.info(
+ "verfied the mitmproxy-cert is installed in the nss cert db on android"
+ )
+ return True
+ return False
+
+ def certutil(self, args, raise_exception=True):
+ cmd = [self.certutil_path] + list(args)
+ LOG.info("Certutil: Running command: %s" % " ".join(cmd))
+ try:
+ cmd_proc = subprocess.Popen(
+ cmd, stdout=PIPE, stderr=PIPE, env=os.environ.copy()
+ )
+
+ cmd_output, errs = cmd_proc.communicate()
+ except subprocess.SubprocessError:
+ LOG.critical("could not run the certutil command")
+ raise
+
+ if cmd_proc.returncode == 0:
+ # Debug purpose only remove if stable
+ LOG.info("Certutil returncode: %s" % cmd_proc.returncode)
+ LOG.info("Certutil output: %s" % cmd_output)
+ return cmd_output
+ else:
+ if raise_exception:
+ LOG.critical("Certutil command failed!!")
+ LOG.info("Certutil returncode: %s" % cmd_proc.returncode)
+ LOG.info("Certutil output: %s" % cmd_output)
+ LOG.info("Certutil error: %s" % errs)
+ raise Exception("Certutil command failed!!")
+ else:
+ return False