Skip to content
This repository has been archived by the owner on Apr 15, 2024. It is now read-only.

c-fraser/mitmify

Repository files navigation

mitmify

Test Release Maven Central Javadoc Apache License 2.0

mitmify is an HTTP(S) 1.x proxy server which enables traffic to be intercepted and dynamically transformed.

Usage

The mitmify library is accessible via Maven Central.

mitmify requires Java 17+.

Design

The proxy Server proxies requests as displayed in the diagram below.

proxy-server

The first Interceptor that finds the Request to be interceptable is used to intercept the Request and Response. As such, be mindful of the Interceptor order in Server.create. Interceptor instances with overly generic interceptable implementations should be nearer to the end of the provided array of Interceptor, otherwise they may supersede the correct Interceptor.

The Proxier handles the execution of the proxy request. To customize how proxy requests are executed, a Proxier implementation may be specified when creating the Server or within an Interceptor.

To proxy HTTPS requests, the Server must be created with a CA certificate and (the corresponding) private key. Additionally, the given CA certificate must be trusted by the (proxy) client(s). This is necessary to be able to establish a TLS connection between the client and proxy Server, so the Server can decrypt and intercept the Request. The TLS related interactions of proxying an HTTPS request is depicted below.

https

Examples

Intercept a proxied HTTP response

// Initialize a mock web server which is the target for proxy requests
MockWebServer().use { target ->
  // Enqueue a mock response for the proxied request
  target.enqueue(MockResponse().setBody("Hello!"))
  // Define a response interceptor to modify the proxy response
  class ResponseInterceptor : Interceptor {
    // Use this interceptor for "hello" requests
    override fun interceptable(request: Request) = request.uri.path == "/hello"
    override fun intercept(response: Response) {
      // Print the response from the proxy request
      response.body?.let(::String)?.also { println("Intercepted: $it") }
      // Change the proxy response body
      response.body = "Goodbye!".toByteArray()
    }
  }
  // Create and start a proxy server
  Server.create(ResponseInterceptor()).start(PORT).use { _ ->
    // Initialize an HTTP client that uses the proxy server
    val client =
      OkHttpClient.Builder().proxySelector(ProxySelector.of(InetSocketAddress(PORT))).build()
    // Execute a request then print the (intercepted) response body
    client
      .newCall(okhttp3.Request.Builder().url(target.url("/hello")).build())
      .execute()
      .use { response -> response.body?.use(ResponseBody::string) }
      ?.also { println("Received: $it") }
  }
}
Intercepted: Hello!
Received: Goodbye!

Proxy an HTTPS request

The HTTPS examples use okhttp-tls to simplify the creation of certificates.

Proxy server uses a trusted certificate chain

This example code is intended to be representative of a realistic HTTPS deployment.

// Create a root certificate authority
val rootCertificate = HeldCertificate.Builder().certificateAuthority(1).build()
// Create an intermediate certificate authority (signed by the root certificate)
val intermediateCertificate =
  HeldCertificate.Builder().certificateAuthority(0).signedBy(rootCertificate).build()
// Create a client certificates that trust the root certificate
val clientCertificates =
  HandshakeCertificates.Builder().addTrustedCertificate(rootCertificate.certificate).build()
// Create and start a proxy server which uses the intermediate certificate authority to generate
// trusted certificates for the (destination of) proxy requests
Server.create(
  // To proxy HTTPS requests the proxy server requires a CA certificate and private key.
  // The proxy client(s) must trust the provided CA certificate so the proxy server can generate a
  // (trusted) certificate to establish a TLS connection (to access the proxy request)
  certificatePath = intermediateCertificate.certificatePem().asFile("proxy.pem"),
  privateKeyPath = intermediateCertificate.privateKeyPkcs8Pem().asFile("proxy.key")
)
  .start(PORT)
  .use { _ ->
    // Initialize an HTTPS client that uses the proxy server and client certificates
    val client =
      OkHttpClient.Builder()
        .proxySelector(ProxySelector.of(InetSocketAddress(PORT)))
        .sslSocketFactory(
          clientCertificates.sslSocketFactory(), clientCertificates.trustManager
        )
        .build()
    // Execute an HTTPS request and expect a successful response code
    client
      .newCall(Request.Builder().url("https://httpbin.org/get").build())
      .execute()
      .use(Response::isSuccessful)
      .also(::println)
  }

Proxy server executes a request with mTLS

// Create a root certificate for the client and server to trust
val rootCertificate = HeldCertificate.Builder().certificateAuthority(0).build()
// Create a server certificate (signed by the root certificate) for the mock web server
val serverCertificate =
  HandshakeCertificates.Builder()
    .addTrustedCertificate(rootCertificate.certificate)
    .heldCertificate(
      HeldCertificate.Builder()
        .addSubjectAlternativeName(localhost)
        .signedBy(rootCertificate)
        .build()
    )
    .build()
// Create a client certificate (signed by the root certificate) for the client
val clientCertificate =
  HandshakeCertificates.Builder()
    .addTrustedCertificate(rootCertificate.certificate)
    .heldCertificate(HeldCertificate.Builder().signedBy(rootCertificate).build())
    .build()
// Initialize an HTTPS mock web server which is the target for proxy requests
MockWebServer()
  .apply { useHttps(serverCertificate.sslSocketFactory(), false) }
  .apply { requestClientAuth() }
  .use { target ->
    target.enqueue(MockResponse())
    // Create a proxier that uses the client certificates
    val proxier =
      Proxier.create(
        OkHttpClient.Builder()
          .sslSocketFactory(
            clientCertificate.sslSocketFactory(), clientCertificate.trustManager
          )
          .build()
      )
    // Create and start the proxy server that can connect to the mock web server
    Server.create(
      proxier = proxier,
      // Provide a certificate and private key to proxy HTTPS requests
      certificatePath = proxyCertPath,
      privateKeyPath = proxyKeyPath
    )
      .start(PORT)
      .use { _ ->
        // Initialize an HTTPS client that uses the proxy server and trusts its certificate
        val client =
          OkHttpClient.Builder()
            .proxySelector(ProxySelector.of(InetSocketAddress(PORT)))
            .sslSocketFactory(
              clientSocketFactory,
              clientTrustManager
            )
            .build()
        // Execute an HTTPS the request to the target and expect response to be successful
        client
          .newCall(Request.Builder().url(target.url("/")).build())
          .execute()
          .use(Response::isSuccessful)
          .also(::println)
      }
  }

License

Copyright 2022 c-fraser

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Acknowledgements

Kudos to mitmproxy and proxyee which significantly influenced the implementation of mitmify.