Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow configuring certain CA details, fixes #5386 #5441

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased: mitmproxy next

* Added new options `ca_basename`, `ca_organization` and `ca_common_name`
([#5386](https://github.com/mitmproxy/mitmproxy/issues/5386), @Prinzhorn)

## 28 June 2022: mitmproxy 8.1.1

* Support specifying the local address for outgoing connections
Expand Down
10 changes: 4 additions & 6 deletions mitmproxy/addons/onboardingapp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@

from flask import Flask, render_template

from mitmproxy.options import CONF_BASENAME, CONF_DIR
from mitmproxy import ctx

app = Flask(__name__)
# will be overridden in the addon, setting this here so that the Flask app can be run standalone.
app.config["CONFDIR"] = CONF_DIR


@app.route("/")
def index():
return render_template("index.html")
return render_template("index.html", ca_basename=ctx.options.ca_basename)


@app.route("/cert/pem")
Expand All @@ -30,8 +28,8 @@ def cer():


def read_cert(ext, content_type):
filename = CONF_BASENAME + f"-ca-cert.{ext}"
p = os.path.join(app.config["CONFDIR"], filename)
filename = ctx.options.ca_basename + f"-ca-cert.{ext}"
p = os.path.join(ctx.options.confdir, filename)
p = os.path.expanduser(p)
with open(p, "rb") as f:
cert = f.read()
Expand Down
18 changes: 9 additions & 9 deletions mitmproxy/addons/onboardingapp/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ <h3 class="my-4">Install mitmproxy's Certificate Authority</h3>
{% include 'icons/' + icon + '-brands.svg' %}
<div class="media-body">
<h5 class="mt-0">{{ title | safe }}</h5>
<a class="btn btn-sm btn-success" href="/cert/{{ filetype }}" target="_blank">🔏 Get mitmproxy-ca-cert.{{
<a class="btn btn-sm btn-success" href="/cert/{{ filetype }}" target="_blank">🔏 Get {{ ca_basename }}-ca-cert.{{
filetype }}</a>
<a class="btn btn-sm btn-info show-instructions" href="#{{ title.split(' ')[0] }}" id="{{ title.split(' ')[0] }}">📖
Show Instructions</a>
Expand Down Expand Up @@ -39,29 +39,29 @@ <h5>Manual Installation</h5>
</ol>
<h5>Automated Installation</h5>
<ol>
<li>Run <code>certutil.exe -addstore root mitmproxy-ca-cert.cer</code>
<li>Run <code>certutil.exe -addstore root {{ ca_basename }}-ca-cert.cer</code>
(<a href="https://technet.microsoft.com/en-us/library/cc732443.aspx">details</a>).
</li>
</ol>
{% endcall %}
{% call entry('Linux', 'linux') %}
<h5>Ubuntu/Debian</h5>
<ol>
<li><code>mv mitmproxy-ca-cert.pem /usr/local/share/ca-certificates/mitmproxy.crt</code></li>
<li><code>mv {{ ca_basename }}-ca-cert.pem /usr/local/share/ca-certificates/{{ ca_basename }}.crt</code></li>
<li><code>sudo update-ca-certificates</code></li>
</ol>
{% endcall %}
{% call entry('macOS', 'apple') %}
<h5>Manual Installation</h5>
<ol>
<li>Double-click the PEM file to open the <samp>Keychain Access</samp> application.</li>
<li>Locate the new certificate "mitmproxy" in the list and double-click it.</li>
<li>Locate the new certificate "{{ ca_basename }}" in the list and double-click it.</li>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it actually the basename that is displayed there?

<li>Change <samp>Secure Socket Layer (SSL)</samp> to <samp>Always Trust</samp>.</li>
<li>Close the dialog window and enter your password if prompted.</li>
</ol>
<h5>Automated Installation</h5>
<ol>
<li><code>sudo security add-trusted-cert -d -p ssl -p basic -k /Library/Keychains/System.keychain mitmproxy-ca-cert.pem</code></li>
<li><code>sudo security add-trusted-cert -d -p ssl -p basic -k /Library/Keychains/System.keychain {{ ca_basename }}-ca-cert.pem</code></li>
</ol>
{% endcall %}
{% call entry('iOS <small>– please read the instructions!</small>', 'apple') %}
Expand All @@ -70,14 +70,14 @@ <h5>iOS 13+</h5>
<li>Use Safari to download the certificate. Other browsers may not open the proper installation prompt.</li>
<li>Install the new Profile (<samp>Settings -> General -> VPN & Device Management</samp>).</li>
<li><span class="text-danger"><strong>Important:</strong> Go to <samp>Settings -> General -> About -> Certificate Trust Settings</samp>.
Toggle <samp>mitmproxy</samp> to <samp>ON</samp>.</span></li>
Toggle <samp>{{ ca_basename }}</samp> to <samp>ON</samp>.</span></li>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it actually the basename that is displayed there?

</ol>
{% endcall %}
{% call entry('Android', 'android', 'cer') %}
<h5>Android 10+</h5>
<ol class="mb-2">
<li>Open the downloaded CER file.</li>
<li>Enter <samp>mitmproxy</samp> (or anything else) as the certificate name.</li>
<li>Enter <samp>{{ ca_basename }}</samp> (or anything else) as the certificate name.</li>
<li>For credential use, select <samp>VPN and apps</samp>.</li>
<li>Click OK.</li>
</ol>
Expand Down Expand Up @@ -108,8 +108,8 @@ <h5>Firefox</h5>
{% include 'icons/certificate-solid.svg' %}
<div class="media-body">
<h5 class="mt-0">Other Platforms</h5>
<a class="btn btn-sm btn-success" href="/cert/pem" target="_blank">🔏 Get mitmproxy-ca-cert.pem</a>
<a class="btn btn-sm btn-success" href="/cert/p12" target="_blank">🔏 Get mitmproxy-ca-cert.p12</a>
<a class="btn btn-sm btn-success" href="/cert/pem" target="_blank">🔏 Get {{ ca_basename }}-ca-cert.pem</a>
<a class="btn btn-sm btn-success" href="/cert/p12" target="_blank">🔏 Get {{ ca_basename }}-ca-cert.p12</a>
</div>
</li>
</ul>
Expand Down
13 changes: 11 additions & 2 deletions mitmproxy/addons/tlsconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from OpenSSL import SSL
from mitmproxy import certs, ctx, exceptions, connection, tls
from mitmproxy.net import tls as net_tls
from mitmproxy.options import CONF_BASENAME
from mitmproxy.proxy import context
from mitmproxy.proxy.layers import modes
from mitmproxy.proxy.layers import tls as proxy_tls
Expand Down Expand Up @@ -302,11 +301,21 @@ def configure(self, updated):
if "confdir" not in updated and "certs" not in updated:
return

# We don't want users to easily hide the fact that mitmproxy is intercepting the connection.
# If the organization is customized, always append the default to it.
if ctx.options.has_changed("ca_organization"):
default_organization = ctx.options.default("ca_organization")
organization = f"{ctx.options.ca_organization} (via {default_organization})"
else:
organization = ctx.options.ca_organization

certstore_path = os.path.expanduser(ctx.options.confdir)
self.certstore = certs.CertStore.from_store(
path=certstore_path,
basename=CONF_BASENAME,
basename=ctx.options.ca_basename,
key_size=ctx.options.key_size,
organization=organization,
common_name=ctx.options.ca_common_name,
passphrase=ctx.options.cert_passphrase.encode("utf8")
if ctx.options.cert_passphrase
else None,
Expand Down
20 changes: 14 additions & 6 deletions mitmproxy/certs.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def _name_to_keyval(name: x509.Name) -> list[tuple[str, str]]:

def create_ca(
organization: str,
cn: str,
common_name: str,
key_size: int,
) -> tuple[rsa.RSAPrivateKeyWithSerialization, x509.Certificate]:
now = datetime.datetime.now()
Expand All @@ -183,7 +183,7 @@ def create_ca(
) # type: ignore
name = x509.Name(
[
x509.NameAttribute(NameOID.COMMON_NAME, cn),
x509.NameAttribute(NameOID.COMMON_NAME, common_name),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, organization),
]
)
Expand Down Expand Up @@ -349,13 +349,15 @@ def from_store(
path: Union[Path, str],
basename: str,
key_size: int,
organization: Optional[str] = None,
common_name: Optional[str] = None,
passphrase: Optional[bytes] = None,
) -> "CertStore":
path = Path(path)
ca_file = path / f"{basename}-ca.pem"
dhparam_file = path / f"{basename}-dhparam.pem"
if not ca_file.exists():
cls.create_store(path, basename, key_size)
cls.create_store(path, basename, key_size, organization, common_name)
return cls.from_files(ca_file, dhparam_file, passphrase)

@classmethod
Expand Down Expand Up @@ -390,16 +392,22 @@ def umask_secret():

@staticmethod
def create_store(
path: Path, basename: str, key_size: int, organization=None, cn=None
path: Path,
basename: str,
key_size: int,
organization: Optional[str] = None,
common_name: Optional[str] = None,
) -> None:
path.mkdir(parents=True, exist_ok=True)

organization = organization or basename
cn = cn or basename
common_name = common_name or basename

key: rsa.RSAPrivateKeyWithSerialization
ca: x509.Certificate
key, ca = create_ca(organization=organization, cn=cn, key_size=key_size)
key, ca = create_ca(
organization=organization, common_name=common_name, key_size=key_size
)

# Dump the CA plus private key.
with CertStore.umask_secret():
Expand Down
19 changes: 18 additions & 1 deletion mitmproxy/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from mitmproxy import optmanager

CONF_DIR = "~/.mitmproxy"
CONF_BASENAME = "mitmproxy"
LISTEN_PORT = 8080
CONTENT_VIEW_LINES_CUTOFF = 512
KEY_SIZE = 2048
Expand Down Expand Up @@ -33,6 +32,24 @@ def __init__(self, **kwargs) -> None:
that will be served to the proxy client, as extras.
""",
)
self.add_option(
"ca_basename",
str,
"mitmproxy",
"Basename (prefix) of the certificate files.",
)
self.add_option(
"ca_organization",
str,
"mitmproxy",
'Organization of the generated certificate authority. Will always include "mitmproxy"',
)
self.add_option(
"ca_common_name",
str,
"mitmproxy",
"Common name of the generated certificate authority.",
)
self.add_option(
"confdir",
str,
Expand Down
6 changes: 6 additions & 0 deletions web/src/js/ducks/_options_gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ export interface OptionsState {
block_list: string[]
block_private: boolean
body_size_limit: string | undefined
ca_basename: string
ca_common_name: string
ca_organization: string
cert_passphrase: string | undefined
certs: string[]
ciphers_client: string | undefined
Expand Down Expand Up @@ -102,6 +105,9 @@ export const defaultState: OptionsState = {
block_list: [],
block_private: false,
body_size_limit: undefined,
ca_basename: "mitmproxy",
ca_common_name: "mitmproxy",
ca_organization: "mitmproxy",
cert_passphrase: undefined,
certs: [],
ciphers_client: undefined,
Expand Down