Skip to content

Commit d98b823

Browse files
authored
Merge pull request #52 from hatoo/cache-cert
cache cert
2 parents 5415403 + 4743cd6 commit d98b823

File tree

8 files changed

+196
-36
lines changed

8 files changed

+196
-36
lines changed

Cargo.lock

Lines changed: 105 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ tracing = "0.1.40"
3434
hyper-util = { version = "0.1.7", features = ["tokio"] }
3535
native-tls = { version = "0.2.12", features = ["alpn"] }
3636
thiserror = "1.0.62"
37+
moka = { version = "0.12.8", features = ["sync"] }
3738

3839
[dev-dependencies]
3940
axum = { version = "0.7.2", features = ["http2"] }

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use std::path::PathBuf;
1515
1616
use clap::{Args, Parser};
1717
use http_mitm_proxy::{DefaultClient, MitmProxy};
18+
use moka::sync::Cache;
1819
use tracing_subscriber::EnvFilter;
1920
2021
#[derive(Parser)]
@@ -82,6 +83,7 @@ async fn main() {
8283
let proxy = MitmProxy::new(
8384
// This is the root cert that will be used to sign the fake certificates
8485
Some(root_cert),
86+
Some(Cache::new(128)),
8587
);
8688
8789
let client = DefaultClient::new(

examples/dev_proxy.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use clap::{Args, Parser};
66
use http_body_util::{BodyExt, Full};
77
use http_mitm_proxy::{DefaultClient, MitmProxy};
88
use hyper::Response;
9+
use moka::sync::Cache;
910

1011
#[derive(Parser)]
1112
struct Opt {
@@ -76,6 +77,7 @@ async fn main() {
7677
let proxy = MitmProxy::new(
7778
// This is the root cert that will be used to sign the fake certificates
7879
Some(root_cert),
80+
Some(Cache::new(128)),
7981
);
8082

8183
let client = DefaultClient::new(

examples/proxy.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::path::PathBuf;
22

33
use clap::{Args, Parser};
44
use http_mitm_proxy::{DefaultClient, MitmProxy};
5+
use moka::sync::Cache;
56
use tracing_subscriber::EnvFilter;
67

78
#[derive(Parser)]
@@ -69,6 +70,7 @@ async fn main() {
6970
let proxy = MitmProxy::new(
7071
// This is the root cert that will be used to sign the fake certificates
7172
Some(root_cert),
73+
Some(Cache::new(128)),
7274
);
7375

7476
let client = DefaultClient::new(

src/lib.rs

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ use hyper::{
99
Method, Request, Response, StatusCode,
1010
};
1111
use hyper_util::rt::{TokioExecutor, TokioIo};
12+
use moka::sync::Cache;
1213
use std::{borrow::Borrow, future::Future, net::SocketAddr, sync::Arc};
13-
use tls::server_config;
14+
use tls::{generate_cert, CertifiedKeyDer};
1415
use tokio::net::{TcpListener, TcpStream, ToSocketAddrs};
1516

1617
pub use futures;
1718
pub use hyper;
19+
pub use moka;
1820
pub use tokio_native_tls;
1921

2022
pub mod default_client;
@@ -29,12 +31,20 @@ pub struct MitmProxy<C> {
2931
///
3032
/// If None, proxy will just tunnel HTTPS traffic and will not observe HTTPS traffic.
3133
pub root_cert: Option<C>,
34+
/// Cache to store generated certificates. If None, cache will not be used.
35+
/// If root_cert is None, cache will not be used.
36+
///
37+
/// The key of cache is hostname.
38+
pub cert_cache: Option<Cache<String, CertifiedKeyDer>>,
3239
}
3340

3441
impl<C> MitmProxy<C> {
3542
/// Create a new MitmProxy
36-
pub fn new(root_cert: Option<C>) -> Self {
37-
Self { root_cert }
43+
pub fn new(root_cert: Option<C>, cache: Option<Cache<String, CertifiedKeyDer>>) -> Self {
44+
Self {
45+
root_cert,
46+
cert_cache: cache,
47+
}
3848
}
3949
}
4050

@@ -119,15 +129,20 @@ impl<C: Borrow<rcgen::CertifiedKey> + Send + Sync + 'static> MitmProxy<C> {
119129
);
120130
return;
121131
};
122-
if let Some(root_cert) = proxy.root_cert.as_ref() {
123-
let Ok(server_config) =
124-
// Even if URL is modified by middleman, we should sign with original host name to communicate client.
125-
server_config(connect_authority.host().to_string(), root_cert.borrow(), true)
126-
else {
127-
tracing::error!("Failed to create server config for {}", connect_authority.host());
128-
return;
132+
if let Some(server_config) =
133+
proxy.server_config(connect_authority.host().to_string(), true)
134+
{
135+
let server_config = match server_config {
136+
Ok(server_config) => server_config,
137+
Err(err) => {
138+
tracing::error!(
139+
"Failed to create server config for {}, {}",
140+
connect_authority.host(),
141+
err
142+
);
143+
return;
144+
}
129145
};
130-
// TODO: Cache server_config
131146
let server_config = Arc::new(server_config);
132147
let tls_acceptor = tokio_rustls::TlsAcceptor::from(server_config);
133148
let client = match tls_acceptor.accept(TokioIo::new(client)).await {
@@ -189,6 +204,48 @@ impl<C: Borrow<rcgen::CertifiedKey> + Send + Sync + 'static> MitmProxy<C> {
189204
.map(|res| res.map(|b| b.boxed()))
190205
}
191206
}
207+
208+
fn get_certified_key(&self, host: String) -> Option<CertifiedKeyDer> {
209+
if let Some(root_cert) = self.root_cert.as_ref() {
210+
Some(if let Some(cache) = self.cert_cache.as_ref() {
211+
cache.get_with(host.clone(), move || {
212+
generate_cert(host, root_cert.borrow())
213+
})
214+
} else {
215+
generate_cert(host, root_cert.borrow())
216+
})
217+
} else {
218+
None
219+
}
220+
}
221+
222+
fn server_config(
223+
&self,
224+
host: String,
225+
h2: bool,
226+
) -> Option<Result<rustls::ServerConfig, rustls::Error>> {
227+
if let Some(cert) = self.get_certified_key(host) {
228+
let config = rustls::ServerConfig::builder()
229+
.with_no_client_auth()
230+
.with_single_cert(
231+
vec![rustls::pki_types::CertificateDer::from(cert.cert_der)],
232+
rustls::pki_types::PrivateKeyDer::Pkcs8(
233+
rustls::pki_types::PrivatePkcs8KeyDer::from(cert.key_der),
234+
),
235+
);
236+
237+
Some(if h2 {
238+
config.map(|mut server_config| {
239+
server_config.alpn_protocols = vec!["h2".into(), "http/1.1".into()];
240+
server_config
241+
})
242+
} else {
243+
config
244+
})
245+
} else {
246+
None
247+
}
248+
}
192249
}
193250

194251
fn no_body<E>(status: StatusCode) -> Response<BoxBody<Bytes, E>> {

src/tls.rs

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
pub fn server_config(
2-
host: String,
3-
root_cert: &rcgen::CertifiedKey,
4-
h2: bool,
5-
) -> Result<rustls::ServerConfig, rustls::Error> {
1+
#[derive(Debug, Clone)]
2+
pub struct CertifiedKeyDer {
3+
pub cert_der: Vec<u8>,
4+
/// Pkcs8
5+
pub key_der: Vec<u8>,
6+
}
7+
8+
pub fn generate_cert(host: String, root_cert: &rcgen::CertifiedKey) -> CertifiedKeyDer {
69
let mut cert_params = rcgen::CertificateParams::new(vec![host]).unwrap();
710
cert_params
811
.key_usages
@@ -14,27 +17,14 @@ pub fn server_config(
1417
.extended_key_usages
1518
.push(rcgen::ExtendedKeyUsagePurpose::ClientAuth);
1619

17-
let private_key = rcgen::KeyPair::generate().unwrap();
20+
let key_pair = rcgen::KeyPair::generate().unwrap();
1821

1922
let cert = cert_params
20-
.signed_by(&private_key, &root_cert.cert, &root_cert.key_pair)
23+
.signed_by(&key_pair, &root_cert.cert, &root_cert.key_pair)
2124
.unwrap();
2225

23-
let config = rustls::ServerConfig::builder()
24-
.with_no_client_auth()
25-
.with_single_cert(
26-
vec![rustls::pki_types::CertificateDer::from(cert)],
27-
rustls::pki_types::PrivateKeyDer::Pkcs8(rustls::pki_types::PrivatePkcs8KeyDer::from(
28-
private_key.serialize_der(),
29-
)),
30-
);
31-
32-
if h2 {
33-
config.map(|mut server_config| {
34-
server_config.alpn_protocols = vec!["h2".into(), "http/1.1".into()];
35-
server_config
36-
})
37-
} else {
38-
config
26+
CertifiedKeyDer {
27+
cert_der: cert.der().to_vec(),
28+
key_der: key_pair.serialize_der(),
3929
}
4030
}

0 commit comments

Comments
 (0)