Skip to content

Commit b062874

Browse files
committed
starknet_transaction_prover: add TLS integration tests
Adds end-to-end HTTPS tests against a self-signed test certificate checked into resources/test_tls/: a successful HTTPS JSON-RPC roundtrip on starknet_specVersion, a plain-HTTP-against-TLS-server negative test, and unit tests around load_tls_acceptor covering missing cert/key paths, invalid PEM contents, and the happy path. To allow co-located tests in tls_test.rs to call it, load_tls_acceptor is bumped from private to pub(crate); no API surface is exposed outside the crate.
1 parent 3bf5954 commit b062874

6 files changed

Lines changed: 230 additions & 1 deletion

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Test TLS Material
2+
3+
**This directory contains a self-signed certificate and its private key used
4+
exclusively by the unit tests in `src/server/tls_test.rs`.**
5+
6+
The key is intentionally checked into the repository — it is a test fixture,
7+
not a secret. Do not reuse this material outside of test code.
8+
9+
## Properties
10+
11+
- Subject: `CN=localhost`
12+
- SAN: `DNS:localhost,IP:127.0.0.1`
13+
- Validity: 100 years from generation
14+
- Key: 2048-bit RSA, unencrypted
15+
16+
## Regenerating
17+
18+
```bash
19+
openssl req -x509 -newkey rsa:2048 \
20+
-keyout crates/starknet_transaction_prover/resources/test_tls/key.pem \
21+
-out crates/starknet_transaction_prover/resources/test_tls/cert.pem \
22+
-sha256 -days 36500 -nodes \
23+
-subj "/CN=localhost" \
24+
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
25+
```
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDJzCCAg+gAwIBAgIUCfCD8/3lfYaThN2hDz8c4CIbTDowDQYJKoZIhvcNAQEL
3+
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTI2MDUxNzE1Mjk0N1oYDzIxMjYw
4+
NDIzMTUyOTQ3WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB
5+
AQUAA4IBDwAwggEKAoIBAQCcDEcAhUbdyBnUkIGykVCvNtXVg+sHYlvTTYnpasxM
6+
iUyY9jyoLYJlBPZjmNPipDd67REL2fGa7q3hkkkHAy4BxdYIqE2l9fPkX2unY/nS
7+
OWDemwPgOXEorcDWJC/kIVwNtVdqfLKG22d88QUvMe4rqUCA6J32dQv1/UAQ9OKw
8+
9kVQ3NAwGTMG2e61A3Ueur0DBmtap5YSn7IlT3HKQXSkTX08V7MCf8W9N3/Bg01h
9+
a7yAtNiQX66Gs4y/8CsM7eTBnLt4e0MVyhSJ9Zj2Ibpatr/YyWAhaW8p7y4sGo50
10+
3ovQ6isrOO8ayi6d7fYVCI27NDoiSmJ67okfQk0rf3DJAgMBAAGjbzBtMB0GA1Ud
11+
DgQWBBQdz7tV14UJxD6RchjKEDz61wgN/zAfBgNVHSMEGDAWgBQdz7tV14UJxD6R
12+
chjKEDz61wgN/zAPBgNVHRMBAf8EBTADAQH/MBoGA1UdEQQTMBGCCWxvY2FsaG9z
13+
dIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAIiAb1/7gA/NXMck/k3WGnYCp3Aut
14+
rYDbfq1fwG/GJZ572qd9XREtuC198QPpQ04yjv7v2sspeWFqAS8e+Gr67h1cQ7q/
15+
mEnGPXQBKQ9r4TSamsFHnrh5x/J9Ec/hBXU9xd8OemZ+o00Itxn1FWwDBvudzfOA
16+
1IJ/7RNxmhUK2/dOOS9Jo5zGhRX/f6s8qmW6ZX2dRpvio3YqV30LpVSBZMsfQiWv
17+
dRHcl5xVoxxtvSkKt1Ou8VE2SEl61c7+Gb3zPBxOdT59QDFGZvX9PC8fPsOq2dNh
18+
ZhYJ4UJfWP5lMa372GATdPjVlZSf575xDpagUgSEKxEyCd7XC2k/p00u+w==
19+
-----END CERTIFICATE-----
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCcDEcAhUbdyBnU
3+
kIGykVCvNtXVg+sHYlvTTYnpasxMiUyY9jyoLYJlBPZjmNPipDd67REL2fGa7q3h
4+
kkkHAy4BxdYIqE2l9fPkX2unY/nSOWDemwPgOXEorcDWJC/kIVwNtVdqfLKG22d8
5+
8QUvMe4rqUCA6J32dQv1/UAQ9OKw9kVQ3NAwGTMG2e61A3Ueur0DBmtap5YSn7Il
6+
T3HKQXSkTX08V7MCf8W9N3/Bg01ha7yAtNiQX66Gs4y/8CsM7eTBnLt4e0MVyhSJ
7+
9Zj2Ibpatr/YyWAhaW8p7y4sGo503ovQ6isrOO8ayi6d7fYVCI27NDoiSmJ67okf
8+
Qk0rf3DJAgMBAAECggEAQmpDSehvifMhc0Pxv4NzmK85AX/85w6o0F0fBlZrD2Qc
9+
UrnyhQ2hgsdC6o7gF4UXC92cNLQUzYEqRmhRZoem7CA8gUDIk4sDu74U/pBhgmTj
10+
YrsNQkCQdeTFvx51t52vJTJ6OxtJjHYTLK0ULMsOeEy35GWc3Ylhhte7jbv8Q55S
11+
wSwmCqxnEj6+5HlAeOIC2LJnrerDEejlTpXH1HCEsTH0dd4EQ/sHixzNVt2MSHDU
12+
fjhsmTl6kTO+RcIPoohuUSMrtrN9j/T4GxcrZqqwdzws/KMg1GYWvRxBJQHuO1yl
13+
5ivhA4sjE8cmN2SbcgrYa3R7aK3XzrCmxx4DKHNYlQKBgQDWeS4BIVEIKwzjjQOC
14+
RPhPg4Zo+0xYzceq4Q7ERn6mBojsLxNF91Z0Yx32rDpQziV1FESe0xmWneRCIjP8
15+
Ua31MU5FeH1GI7q0RyWD7XoUTI8io8cFdMiQEbRrYqARVrHun6NpBHIUG5Z5az8L
16+
JWRP/P0QN90cgkgLnqwxFicHywKBgQC6Qx7ej5cPBBf/m8Aijmd8cUDqrmk6A4Zc
17+
df8IjsfMdkv7H29qBip4KgNuThREaTq5fuCqgKvVKUgmudEFousfuWjzJsICBE9h
18+
4DGFDUxPBYyUFf70PmlQ8e4avvNg1Cx5VgIt4M2IAsUmiAC/52y14R67u7MfQP7q
19+
UWU3YitPOwKBgQCkq/A9n+YGnn9L68aI7Am3i2XVDzXEbWNj+V8MJpAxS40vwslK
20+
jCjOPhgQgJZZ2p358fDp/W2FLn/Go1pE3jXxr8TIJEYTZ3V/26ybSefU1B+GWjeC
21+
IfOoYl+jn9sE1QrTC7E8/dPVSoVTfpuuJCyMGdP38tyLeiB1A4R0P+0B1wKBgAtZ
22+
79Wsdo5Jt5SyT0FL4G6rEEO9IViRwmx8HHDPEsoZI4RIZCfX/FqaZN8iDwYkS5nm
23+
a5a4hMBW5bjGdkCbryydxhGbeRNaY+QZH6t2JgJi2jBkLsd/zjdKpzImFPr/sz4p
24+
ybQ2ERCK6qzweOs5FVz4PUE/rSjocyCgmUSIzQ7lAoGAE+Vc2EeHPxFRZgVSFS6m
25+
SEl5p5h8Wfr39+HKBSVd1P7QMHAFN11yXrGhIrRnAkI37chSvNtwLbTLicHuY127
26+
zEVAVXATvTZosErLp8WuNCxnb/1PxBVv1RMxJHm0ibjfRuWpDF+Gstn6ESQYKyPU
27+
Z84XK248kL9fShkmRNRujGI=
28+
-----END PRIVATE KEY-----

crates/starknet_transaction_prover/src/server.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ pub mod tls;
3939
mod cors_test;
4040
#[cfg(test)]
4141
mod rpc_spec_test;
42+
#[cfg(test)]
43+
mod tls_test;
4244

4345
#[cfg(test)]
4446
#[path = "server/ohttp_integration_test.rs"]

crates/starknet_transaction_prover/src/server/tls.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ pub async fn start_tls_server(
138138
}
139139

140140
/// Loads a certificate chain and private key from PEM files and builds a TLS acceptor.
141-
fn load_tls_acceptor(cert_path: &Path, key_path: &Path) -> anyhow::Result<TlsAcceptor> {
141+
pub(crate) fn load_tls_acceptor(cert_path: &Path, key_path: &Path) -> anyhow::Result<TlsAcceptor> {
142142
let cert_pem = std::fs::read(cert_path)
143143
.with_context(|| format!("Failed to read TLS certificate file: {}", cert_path.display()))?;
144144
let cert_chain: Vec<CertificateDer<'static>> =
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
//! Integration tests for the TLS server bootstrap.
2+
//!
3+
//! Uses a self-signed certificate checked into `resources/test_tls/`. The cert covers
4+
//! `CN=localhost` (+ SAN `DNS:localhost`) and is valid for 100 years; regenerate with:
5+
//!
6+
//! ```bash
7+
//! openssl req -x509 -newkey rsa:2048 \
8+
//! -keyout crates/starknet_transaction_prover/resources/test_tls/key.pem \
9+
//! -out crates/starknet_transaction_prover/resources/test_tls/cert.pem \
10+
//! -sha256 -days 36500 -nodes \
11+
//! -subj "/CN=localhost" \
12+
//! -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
13+
//! ```
14+
15+
use std::io::Write;
16+
use std::net::SocketAddr;
17+
use std::path::{Path, PathBuf};
18+
19+
use serde_json::Value;
20+
use tempfile::NamedTempFile;
21+
22+
use crate::server::mock_rpc::MockProvingRpc;
23+
use crate::server::rpc_api::ProvingRpcServer;
24+
use crate::server::rpc_impl::SPEC_VERSION;
25+
use crate::server::tls::{load_tls_acceptor, start_tls_server};
26+
27+
/// Installs the default rustls crypto provider (aws-lc-rs) if not already installed.
28+
/// Required by reqwest when using rustls-based TLS.
29+
fn ensure_crypto_provider() {
30+
let _ = tokio_rustls::rustls::crypto::aws_lc_rs::default_provider().install_default();
31+
}
32+
33+
fn test_cert_path() -> PathBuf {
34+
Path::new(env!("CARGO_MANIFEST_DIR")).join("resources/test_tls/cert.pem")
35+
}
36+
37+
fn test_key_path() -> PathBuf {
38+
Path::new(env!("CARGO_MANIFEST_DIR")).join("resources/test_tls/key.pem")
39+
}
40+
41+
/// Reads the checked-in self-signed test certificate as PEM bytes.
42+
fn read_test_cert_pem() -> Vec<u8> {
43+
std::fs::read(test_cert_path()).expect("Failed to read test cert.pem")
44+
}
45+
46+
/// Writes PEM bytes to a temporary file and returns the handle.
47+
fn write_pem_to_tempfile(pem_bytes: &[u8]) -> NamedTempFile {
48+
let mut file = NamedTempFile::new().expect("Failed to create temp file");
49+
file.write_all(pem_bytes).expect("Failed to write PEM");
50+
file.flush().expect("Failed to flush PEM file");
51+
file
52+
}
53+
54+
/// Starts a TLS server with mock RPC methods, returns (addr, server_handle, cert_pem).
55+
async fn start_test_tls_server() -> (SocketAddr, jsonrpsee::server::ServerHandle, Vec<u8>) {
56+
let methods = MockProvingRpc::from_expected_json().into_rpc();
57+
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
58+
59+
let (local_addr, handle) = start_tls_server(
60+
addr,
61+
&test_cert_path(),
62+
&test_key_path(),
63+
methods,
64+
10, // max_connections
65+
5 * 1024 * 1024, // max_request_body_size
66+
None, // cors_layer
67+
None, // ohttp_layer
68+
)
69+
.await
70+
.expect("Failed to start TLS server");
71+
72+
(local_addr, handle, read_test_cert_pem())
73+
}
74+
75+
#[tokio::test]
76+
async fn test_https_spec_version_succeeds() {
77+
ensure_crypto_provider();
78+
let (addr, handle, cert_pem) = start_test_tls_server().await;
79+
80+
let cert = reqwest::tls::Certificate::from_pem(&cert_pem)
81+
.expect("Failed to parse certificate for reqwest");
82+
let client = reqwest::Client::builder()
83+
.add_root_certificate(cert)
84+
.build()
85+
.expect("Failed to build HTTPS client");
86+
87+
let body = serde_json::json!({
88+
"jsonrpc": "2.0",
89+
"id": "1",
90+
"method": "starknet_specVersion"
91+
});
92+
93+
let response = client
94+
.post(format!("https://localhost:{}", addr.port()))
95+
.json(&body)
96+
.send()
97+
.await
98+
.expect("HTTPS request failed");
99+
100+
assert_eq!(response.status(), 200);
101+
102+
let json: Value = response.json().await.expect("Failed to parse response JSON");
103+
assert_eq!(json["result"].as_str().unwrap(), SPEC_VERSION);
104+
105+
handle.stop().expect("Failed to stop server");
106+
}
107+
108+
#[tokio::test]
109+
async fn test_http_to_tls_server_fails() {
110+
ensure_crypto_provider();
111+
let (addr, handle, _cert_pem) = start_test_tls_server().await;
112+
113+
let client = reqwest::Client::new();
114+
let body = serde_json::json!({
115+
"jsonrpc": "2.0",
116+
"id": "1",
117+
"method": "starknet_specVersion"
118+
});
119+
120+
// Plain HTTP to a TLS server should fail (connection or protocol error).
121+
let result = client.post(format!("http://localhost:{}", addr.port())).json(&body).send().await;
122+
123+
assert!(result.is_err(), "Expected HTTP to TLS server to fail, but got: {result:?}");
124+
125+
handle.stop().expect("Failed to stop server");
126+
}
127+
128+
#[test]
129+
fn test_load_tls_acceptor_missing_cert_file() {
130+
let key_file = write_pem_to_tempfile(b"dummy key content");
131+
let result = load_tls_acceptor("/nonexistent/cert.pem".as_ref(), key_file.path());
132+
assert!(result.is_err(), "Expected error for missing cert file");
133+
}
134+
135+
#[test]
136+
fn test_load_tls_acceptor_missing_key_file() {
137+
let cert_file = write_pem_to_tempfile(b"dummy cert content");
138+
let result = load_tls_acceptor(cert_file.path(), "/nonexistent/key.pem".as_ref());
139+
assert!(result.is_err(), "Expected error for missing key file");
140+
}
141+
142+
#[test]
143+
fn test_load_tls_acceptor_invalid_pem() {
144+
let cert_file = write_pem_to_tempfile(b"not a valid PEM certificate");
145+
let key_file = write_pem_to_tempfile(b"not a valid PEM key");
146+
let result = load_tls_acceptor(cert_file.path(), key_file.path());
147+
assert!(result.is_err(), "Expected error for invalid PEM content");
148+
}
149+
150+
#[test]
151+
fn test_load_tls_acceptor_succeeds_for_valid_files() {
152+
// Sanity check that the checked-in test cert/key actually load as a TLS acceptor.
153+
load_tls_acceptor(&test_cert_path(), &test_key_path())
154+
.expect("Expected test cert/key to load successfully");
155+
}

0 commit comments

Comments
 (0)