Skip to content

Commit 496797e

Browse files
committed
Add get-tls-cert command
1 parent c865554 commit 496797e

File tree

5 files changed

+164
-11
lines changed

5 files changed

+164
-11
lines changed

Cargo.lock

Lines changed: 16 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
@@ -14,6 +14,7 @@ clap = { version = "4.5.51", features = ["derive"] }
1414
webpki-roots = "1.0.4"
1515
rustls-pemfile = "2.2.0"
1616
anyhow = "1.0.100"
17+
pem-rfc7468 = { version = "0.7.0", features = ["std"] }
1718

1819
[dev-dependencies]
1920
rcgen = "0.14.5"

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33

44
This is a work-in-progress crate designed to be an alternative to [`cvm-reverse-proxy`](https://github.com/flashbots/cvm-reverse-proxy).
55

6-
It offers two components:
7-
- a proxy server, which accepts TLS connections from a proxy client, sends an attestation and then forwards traffic to a target CVM service.
8-
- a proxy client, which accepts connections from elsewhere, connects to and verifies the attestation from the proxy server, and then forwards traffic to it over TLS.
6+
It has three commands:
7+
- `server` - run a proxy server, which accepts TLS connections from a proxy client, sends an attestation and then forwards traffic to a target CVM service.
8+
- `client` - run a proxy client, which accepts connections from elsewhere, connects to and verifies the attestation from the proxy server, and then forwards traffic to it over TLS.
9+
- `get-tls-cert` - connects to a proxy-server, verify the attestation, and if successful write the server's PEM-encoded TLS certificate chain to standard out. This can be used to make subsequent connections to services using this certificate over regular TLS.
910

1011
Unlike `cvm-reverse-proxy`, this uses post-handshake remote-attested TLS, meaning regular CA-signed TLS certificates can be used.
1112

src/lib.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,64 @@ impl<L: AttestationPlatform, R: AttestationPlatform> ProxyClient<L, R> {
392392
}
393393
}
394394

395+
/// Just get the attested remote certificate, with no client authentication
396+
pub async fn get_tls_cert<R: AttestationPlatform>(
397+
server_address: SocketAddr,
398+
server_name: ServerName<'static>,
399+
remote_attestation_platform: R,
400+
) -> Result<Vec<CertificateDer<'static>>, ProxyError> {
401+
let root_store = RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
402+
let client_config = ClientConfig::builder()
403+
.with_root_certificates(root_store)
404+
.with_no_client_auth();
405+
get_tls_cert_with_config(
406+
server_address,
407+
server_name,
408+
remote_attestation_platform,
409+
client_config.into(),
410+
)
411+
.await
412+
}
413+
414+
async fn get_tls_cert_with_config<R: AttestationPlatform>(
415+
server_address: SocketAddr,
416+
server_name: ServerName<'static>,
417+
remote_attestation_platform: R,
418+
client_config: Arc<ClientConfig>,
419+
) -> Result<Vec<CertificateDer<'static>>, ProxyError> {
420+
let connector = TlsConnector::from(client_config);
421+
422+
let out = TcpStream::connect(server_address).await?;
423+
let mut tls_stream = connector.connect(server_name, out).await?;
424+
425+
let (_io, server_connection) = tls_stream.get_ref();
426+
427+
let mut exporter = [0u8; 32];
428+
server_connection.export_keying_material(
429+
&mut exporter,
430+
EXPORTER_LABEL,
431+
None, // context
432+
)?;
433+
434+
let remote_cert_chain = server_connection
435+
.peer_certificates()
436+
.ok_or(ProxyError::NoCertificate)?
437+
.to_owned();
438+
439+
let mut length_bytes = [0; 4];
440+
tls_stream.read_exact(&mut length_bytes).await?;
441+
let length: usize = u32::from_be_bytes(length_bytes).try_into()?;
442+
443+
let mut buf = vec![0; length];
444+
tls_stream.read_exact(&mut buf).await?;
445+
446+
if remote_attestation_platform.is_cvm() {
447+
remote_attestation_platform.verify_attestation(buf, &remote_cert_chain, exporter)?;
448+
}
449+
450+
Ok(remote_cert_chain)
451+
}
452+
395453
/// An error when running a proxy client or server
396454
#[derive(Error, Debug)]
397455
pub enum ProxyError {
@@ -595,4 +653,43 @@ mod tests {
595653

596654
assert_eq!(buf[..], b"some data"[..]);
597655
}
656+
657+
#[tokio::test]
658+
async fn test_get_tls_cert() {
659+
let target_addr = example_service().await;
660+
let target_name = "name".to_string();
661+
662+
let (cert_chain, private_key) = generate_certificate_chain(target_name.clone());
663+
let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key);
664+
665+
let local_attestation_platform = MockAttestation;
666+
667+
let proxy_server = ProxyServer::new_with_tls_config(
668+
cert_chain.clone(),
669+
server_config,
670+
"127.0.0.1:0",
671+
target_addr,
672+
local_attestation_platform,
673+
NoAttestation,
674+
)
675+
.await
676+
.unwrap();
677+
678+
let proxy_server_addr = proxy_server.local_addr().unwrap();
679+
680+
tokio::spawn(async move {
681+
proxy_server.accept().await.unwrap();
682+
});
683+
684+
let retrieved_chain = get_tls_cert_with_config(
685+
proxy_server_addr,
686+
target_name.try_into().unwrap(),
687+
MockAttestation,
688+
client_config,
689+
)
690+
.await
691+
.unwrap();
692+
693+
assert_eq!(retrieved_chain, cert_chain);
694+
}
598695
}

src/main.rs

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,24 @@ use clap::{Parser, Subcommand};
33
use std::{fs::File, net::SocketAddr, path::PathBuf};
44
use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer};
55

6-
use attested_tls_proxy::{MockAttestation, NoAttestation, ProxyClient, ProxyServer, TlsCertAndKey};
6+
use attested_tls_proxy::{
7+
get_tls_cert, MockAttestation, NoAttestation, ProxyClient, ProxyServer, TlsCertAndKey,
8+
};
79

810
#[derive(Parser, Debug, Clone)]
911
#[clap(version, about, long_about = None)]
1012
struct Cli {
1113
#[clap(subcommand)]
1214
command: CliCommand,
13-
/// Socket address to listen on
14-
#[arg(short, long)]
15-
address: SocketAddr,
1615
}
1716

1817
#[derive(Subcommand, Debug, Clone)]
1918
enum CliCommand {
2019
/// Run a proxy client
2120
Client {
21+
/// Socket address to listen on
22+
#[arg(short, long)]
23+
address: SocketAddr,
2224
/// The socket address of the proxy server
2325
#[arg(short, long)]
2426
server_address: SocketAddr,
@@ -34,6 +36,9 @@ enum CliCommand {
3436
},
3537
/// Run a proxy server
3638
Server {
39+
/// Socket address to listen on
40+
#[arg(short, long)]
41+
address: SocketAddr,
3742
/// Socket address of the target service to forward traffic to
3843
#[arg(short, long)]
3944
target_address: SocketAddr,
@@ -48,6 +53,15 @@ enum CliCommand {
4853
#[arg(long)]
4954
client_auth: bool,
5055
},
56+
/// Retrieve the attested TLS certificate from a proxy server
57+
GetTlsCert {
58+
/// The socket address of the proxy server
59+
#[arg(short, long)]
60+
server_address: SocketAddr,
61+
/// The domain name of the proxy server
62+
#[arg(long)]
63+
server_name: String,
64+
},
5165
}
5266

5367
#[tokio::main]
@@ -56,6 +70,7 @@ async fn main() -> anyhow::Result<()> {
5670

5771
match cli.command {
5872
CliCommand::Client {
73+
address,
5974
server_name,
6075
server_address,
6176
private_key,
@@ -76,7 +91,7 @@ async fn main() -> anyhow::Result<()> {
7691

7792
let client = ProxyClient::new(
7893
tls_cert_and_chain,
79-
cli.address,
94+
address,
8095
server_address,
8196
server_name.try_into()?,
8297
NoAttestation,
@@ -91,6 +106,7 @@ async fn main() -> anyhow::Result<()> {
91106
}
92107
}
93108
CliCommand::Server {
109+
address,
94110
target_address,
95111
private_key,
96112
cert_chain,
@@ -102,7 +118,7 @@ async fn main() -> anyhow::Result<()> {
102118

103119
let server = ProxyServer::new(
104120
tls_cert_and_chain,
105-
cli.address,
121+
address,
106122
target_address,
107123
local_attestation,
108124
remote_attestation,
@@ -116,7 +132,17 @@ async fn main() -> anyhow::Result<()> {
116132
}
117133
}
118134
}
135+
CliCommand::GetTlsCert {
136+
server_address,
137+
server_name,
138+
} => {
139+
let cert_chain =
140+
get_tls_cert(server_address, server_name.try_into()?, MockAttestation).await?;
141+
println!("{}", certs_to_pem_string(&cert_chain)?);
142+
}
119143
}
144+
145+
Ok(())
120146
}
121147

122148
/// Load TLS details from storage
@@ -129,12 +155,12 @@ fn load_tls_cert_and_key(
129155
Ok(TlsCertAndKey { key, cert_chain })
130156
}
131157

132-
pub fn load_certs_pem(path: PathBuf) -> std::io::Result<Vec<CertificateDer<'static>>> {
158+
fn load_certs_pem(path: PathBuf) -> std::io::Result<Vec<CertificateDer<'static>>> {
133159
rustls_pemfile::certs(&mut std::io::BufReader::new(File::open(path)?))
134160
.collect::<Result<Vec<_>, _>>()
135161
}
136162

137-
pub fn load_private_key_pem(path: PathBuf) -> anyhow::Result<PrivateKeyDer<'static>> {
163+
fn load_private_key_pem(path: PathBuf) -> anyhow::Result<PrivateKeyDer<'static>> {
138164
let mut reader = std::io::BufReader::new(File::open(path)?);
139165

140166
// Tries to read the key as PKCS#8, PKCS#1, or SEC1
@@ -144,3 +170,15 @@ pub fn load_private_key_pem(path: PathBuf) -> anyhow::Result<PrivateKeyDer<'stat
144170

145171
Ok(PrivateKeyDer::Pkcs8(pks8_key))
146172
}
173+
174+
/// Given a certificate chain, convert it to a PEM encoded string
175+
fn certs_to_pem_string(certs: &[CertificateDer<'_>]) -> Result<String, pem_rfc7468::Error> {
176+
let mut out = String::new();
177+
for cert in certs {
178+
let block =
179+
pem_rfc7468::encode_string("CERTIFICATE", pem_rfc7468::LineEnding::LF, cert.as_ref())?;
180+
out.push_str(&block);
181+
out.push('\n');
182+
}
183+
Ok(out)
184+
}

0 commit comments

Comments
 (0)