proxymate is a simple proxy written in async Rust that makes it possible to tunnel network traffic between plain TCP, TLS and/or WebSockets one to another.
The emphasized features are simplicity, reliability and scalability.
Proxymate picks rustls crate as the TLS library because of its quality (see audit), being written in Rust, simple, without dependency on OpenSSL. A WebSockets crate tungstenite is then natural choice for its compatibility with rustls.
Both crates have their async bindings in async_tls, async_tungstenite and ws_stream_tungstenite. With async_std they allow to use uniform interface of AsyncRead
and AsyncWrite
on various socket types.
The current version does not support text messages on WebSockets due to a limitation in ws_stream_tungstenite. This could be fixed in upcoming versions if needed.
Warning: The current version has not yet been extensively tested nor deployed to production.
+-----------+
TCP/TLS/WS/WSS | | TCP/TLS/WS/WSS
Client <====================>| proxymate |<~~~~~~~~~~~~~~~~~~~> Target (listen)
| |
+-----------+
o accepts connections from clients
o connects to the target for each client
o tunnels network traffic between the client and the target
Program operation:
- proxymate is run with desired options
- proxymate listens at some address and port specified by
--listen-addr
parameter - A client connects to proxymate
- The client and proxymate perform TLS handshake (if
--server-tls
present) - The client and proxymate perform WebSockets handshake (if
--server-ws
present)
- The client and proxymate perform TLS handshake (if
- proxymate connets to the target (specified by
--target-addr
option)- proxymate and the target perform TLS handshake (if
--target-tls
present) - proxymate and the target perform WebSockets handshake (if
--target-ws
present)
- proxymate and the target perform TLS handshake (if
- Data is transferred between the client and target until one of the peers disconnects
Build with cargo
is mandatory because the binary needs version information exported out of Cargo.toml
.
proxymate $ cargo build # append `-r` for release build
USAGE:
proxymate [FLAGS] [OPTIONS] --listen-addr <listen-addr> --target-addr <target-addr>
FLAGS:
-h, --help Prints help information
--server-tls Use TLS for the server
--server-ws Use WebSockets for the server
--target-tls Use TLS for connection to the target
--target-ws Use WebSockets for connection to the target
-V, --version Prints version information
OPTIONS:
--ca-certs <ca-certs> CA Certificates file in PEM format
--handshake-timeout <handshake-timeout>
Timeout in seconds for establishing connection and handshakes [default: 5]
-l, --listen-addr <listen-addr> Server listen address:port
-c, --server-certs <server-certs>... Server certificates file(s) in PEM format
-k, --server-key <server-key> Server private key file in PEM format
-t, --target-addr <target-addr> Target address:port
Detailed description of some options:
-
flags
*-tls
,*-ws
can be arbitrarily combined. If--server-tls
flag is present, then--server-certs
and--server-key
options become mandatory. -
-l, --listen-addr
Hostname or address of a local interface.
0.0.0.0
or just0
for all interfaces127.0.0.1
localhost only
-
-c, --server-certs
List of files separated by spaces. The certificates have to form a chain of trust. The end-entity certificate must have
subjectAltName
extension as required by rustls crate. The root certificate may be present in the chain but it's useless. See Certificates section for further description. -
-k, --server-key
Both PKCS#8 and PCKS#1 formats are accepted.
-
--ca-certs
Client can optionally import authority certificate(s) to its own trust store. More in the Certificates section.
These are examples of possible usage of proxymate
. In subsequent text diagrams the { }
means a single machine or a trusted network boundary.
-
A TLS client wants to communicate with insecured remote server in a secure way
proxymate
acts as a TLS server connecting via plain TCP to a insecured server in a trusted network:(public network) { TLS client } <======TLS======> { proxymate <----plain TCP---> insecured server }
run options:
proxymate --server-tls --server-key 'server-key.pem' \ --server-certs server-cert.pem intermediate-certs.pem \ -l listen_addr:listen_port \ -t target_addr:target_port
-
A WebSockets secure (WSS) client wants to communicate with insecured remote server in a secure way
The same situation as in preceding use case, but
proxymate
acts as a WebSockets server on top of TLS layer.(public network) { WSS client } <======WSS======> { proxymate <---plain TCP---> insecured server }
run options:
proxymate --server-ws --server-tls --server-key 'server-key.pem' \ --server-certs server-cert.pem intermediate-certs.pem \ -l listen_addr:listen_port \ -t target_addr:target_port
-
Two insecured peers want to communicate securely
Each party needs its own
proxymate
instance.proxymate_A
is in plain-TCP server mode connecting toproxymate_B
via TLS,proxymate_B
's role is opposite.(public network) { peer_A <---plain TCP---> proxymate_A } <======TLS======> { proxymate_B <---plain TCP---> peer_B }
run options of instance A:
proxymate --target-tls --ca-certs ca-cert.pem \ -l listen_addr:listen_port \ -t proxymate_B_addr:proxymate_B_port
run options of instance B:
proxymate --server-tls --server-key 'server-key.pem' \ --server-certs server-cert.pem intermediate-certs.pem \ -l listen_addr:listen_port \ -t target_addr:target_port
-
A WebSockets client communicating with a local TCP server
A typical scenario for a web client communicating with a local server. No encryption is needed since both peers are on the same machine.
{ WS client <---WS---> proxymate <---plain TCP---> local server }
run options:
proxymate --server-ws -l listen_addr:listen_port \ -t target_addr:target_port
In order to succesfully establish a TLS session a server must present to the clients its X.509 certificate and prove it is really the server who is owner of that certificate.
A client validates the certificate presented by the server by means of chain of trust. Each certificate has a property Subject and Issuer, so the chain can be viewed as a sequence of (Subject(n), Issuer(n)) pairs with n for end-entity server's certificate and 0 for a root certificate. In the chain each Issuer(i) = Subject(i-1) meaning that certificate i is issued and signed by the preceding certificate i-1 in the chain. Issuer(0) = Subject(0), so root certificates are self-signed and hence they have to be trusted apriori. It is therefore needed that a client has root certificates installed or imported to its trust store (proxymate has --ca-certs
option for this purpose).
Server usually send a certificate chain, starting by its own end-entity certificate followed by one or more intermediate CA certificates up to the root certificate. It does not matter if the root certificate is included in the certificate chain sent by the server, because client has to have imported and trusted the root certificate before.
Server proves ownership of the end-entity certificate by possesion of corresponding private key since any data encrypted by a public key in the certificate can be decrypted only by the private key paired with that public key and any data signed by the private key can be verified by the public key.
There are at least two certificates needed for TLS handshake:
-
Client needs to have a certificate authority (CA) root certificate imported in its trust store.
-
Server needs its end-entity certificate and private key. The certificate has to be signed either by the root certificate itself or by another intermediate certificate forming a chain of trust up to the root certificate.
X.509 certificates can be created with OpenSSL (or LibreSSL) by these steps:
-
Create a private key for CA root certificate:
openssl genrsa 2048 > ca-key.pem
-
Create a CA root certificate using the private key:
openssl req -new -x509 -nodes -days 365000 -key ca-key.pem -out ca-cert.pem
-
Create end-entity certificate request and private key for a server:
openssl req -newkey rsa:2048 -nodes -days 365000 \ -keyout server-key.pem -out server-req.pem
-
Create the end-entity certificate for the server with the mandatory
subjectAltName
extension and sign it by the CA:openssl x509 -req -days 365000 -set_serial 01 -in server-req.pem -out server-cert.pem \ -CA ca-cert.pem -CAkey ca-key.pem \ -extfile <(printf "extendedKeyUsage = serverAuth\nsubjectAltName = DNS:localhost")