mitm-proxy
is an HTTP(S) 1.x proxy server which enables traffic to be intercepted and dynamically
transformed.
The mitm-proxy
library is accessible
via Maven Central.
mitm-proxy
requires Java 17+.
Refer to the mitm-proxy website.
The proxy Server proxies requests as displayed in the diagram below.
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.
// 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!
The HTTPS examples use okhttp-tls to simplify the creation of certificates.
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)
}
// 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)
}
}
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.
Kudos to proxyee which significantly influenced the
implementation of mitm-proxy
.