Skip to content

Commit

Permalink
feat: proper server certificates
Browse files Browse the repository at this point in the history
This is required as webtransport and chrome do not accept self-signed
  • Loading branch information
ten3roberts committed May 16, 2023
1 parent 74cbad5 commit caf7122
Show file tree
Hide file tree
Showing 13 changed files with 161 additions and 50 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ ulid = { version = "1.0.0", features = ["serde"] }
enum_dispatch = "0.3"
uuid = "1.3"
scopeguard = "1.0"
rustls-native-certs = "0.6.2"

#
# WASM dependencies. Should be able to move off these once this all begins to stabilise a little.
Expand Down
7 changes: 7 additions & 0 deletions app/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,13 @@ pub struct HostCli {
/// Pre-cache assets on the proxy
#[arg(long)]
pub proxy_pre_cache_assets: bool,

/// Certificate for TLS
#[arg(long, requires("key"), default_value("localhost.crt"))]
pub cert: PathBuf,
/// Private key for the certificate
#[arg(long, default_value("localhost.key"))]
pub key: PathBuf,
}

impl Cli {
Expand Down
8 changes: 7 additions & 1 deletion app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,16 +289,22 @@ fn main() -> anyhow::Result<()> {
} else {
format!("127.0.0.1:{QUIC_INTERFACE_PORT}").parse()?
}
} else {
} else if let Some(host) = &cli.host() {
let port = server::start(
&runtime,
assets.clone(),
cli.clone(),
project_path.url,
manifest.as_ref().expect("no manifest"),
metadata.as_ref().expect("no build metadata"),
ambient_network::native::server::Crypto {
cert_file: host.cert.clone(),
key_file: host.key.clone(),
},
);
format!("127.0.0.1:{port}").parse()?
} else {
unreachable!()
};

// Time to join!
Expand Down
6 changes: 4 additions & 2 deletions app/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use ambient_ecs::{
World, WorldEventsSystem, WorldStreamCompEvent,
};
use ambient_network::{
native::server::GameServer,
native::server::{Crypto, GameServer},
persistent_resources,
server::{ForkingEvent, ProxySettings, ShutdownEvent},
synced_resources,
Expand Down Expand Up @@ -40,6 +40,7 @@ pub fn start(
project_path: AbsAssetUrl,
manifest: &ambient_project::Manifest,
metadata: &ambient_build::Metadata,
crypto: Crypto,
) -> u16 {
log::info!("Creating server");
let host_cli = cli.host().unwrap();
Expand All @@ -58,7 +59,7 @@ pub fn start(
});
let server = runtime.block_on(async move {
if let Some(port) = quic_interface_port {
GameServer::new_with_port(port, false, proxy_settings)
GameServer::new_with_port(port, false, proxy_settings, &crypto)
.await
.context("failed to create game server with port")
.unwrap()
Expand All @@ -67,6 +68,7 @@ pub fn start(
QUIC_INTERFACE_PORT..(QUIC_INTERFACE_PORT + 10),
false,
proxy_settings,
&crypto,
)
.await
.context("failed to create game server with port in range")
Expand Down
1 change: 1 addition & 0 deletions crates/network/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ enum_dispatch = { workspace = true }
pin-project = "1.0"
uuid = { workspace = true }
scopeguard = { workspace = true }
rustls-native-certs = { workspace = true }

[target.'cfg(not(target_os = "unknown"))'.dependencies]
async-trait = { workspace = true }
Expand Down
79 changes: 36 additions & 43 deletions crates/network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@ use stream::FrameError;
use ambient_rpc::RpcError;
use ambient_std::log_error;
use quinn::{
ClientConfig, ConnectionClose, ConnectionError::ConnectionClosed, Endpoint, ServerConfig,
TransportConfig,
ClientConfig, ConnectionClose, ConnectionError::ConnectionClosed, Endpoint, TransportConfig,
};
use rand::Rng;
use rustls::{Certificate, PrivateKey, RootCertStore};
use rustls::RootCertStore;
use thiserror::Error;

pub use ambient_ecs::generated::components::core::network::{
Expand Down Expand Up @@ -187,6 +186,28 @@ impl NetworkError {
}
}

#[tracing::instrument(level = "info")]
fn load_native_roots() -> RootCertStore {
tracing::info!("Loading native roots");
let mut roots = rustls::RootCertStore::empty();
match rustls_native_certs::load_native_certs() {
Ok(certs) => {
for cert in certs {
let cert = rustls::Certificate(cert.0);
if let Err(e) = roots.add(&cert) {
tracing::error!(?cert, "Failed to parse trust anchor: {}", e);
}
}
}

Err(e) => {
tracing::error!("Failed load any default trust roots: {}", e);
}
};

roots
}

pub fn create_client_endpoint_random_port() -> Option<Endpoint> {
for _ in 0..10 {
let client_port = {
Expand All @@ -196,21 +217,26 @@ pub fn create_client_endpoint_random_port() -> Option<Endpoint> {
let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), client_port);

if let Ok(mut endpoint) = Endpoint::client(client_addr) {
let cert = Certificate(CERT.to_vec());
let mut roots = RootCertStore::empty();
roots.add(&cert).unwrap();
let crypto = rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(roots)
let mut tls_config = rustls::ClientConfig::builder()
.with_safe_default_cipher_suites()
.with_safe_default_kx_groups()
.with_protocol_versions(&[&rustls::version::TLS13])
.unwrap()
.with_root_certificates(load_native_roots())
.with_no_client_auth();

// tls_config.enable_early_data = true;
tls_config.alpn_protocols = vec!["ambient-02".into()];

let mut transport = TransportConfig::default();
transport.keep_alive_interval(Some(Duration::from_secs_f32(1.)));

if std::env::var("AMBIENT_DISABLE_TIMEOUT").is_ok() {
transport.max_idle_timeout(None);
} else {
transport.max_idle_timeout(Some(Duration::from_secs_f32(60.).try_into().unwrap()));
}
let mut client_config = ClientConfig::new(Arc::new(crypto));
let mut client_config = ClientConfig::new(Arc::new(tls_config));
client_config.transport_config(Arc::new(transport));

endpoint.set_default_client_config(client_config);
Expand All @@ -220,39 +246,6 @@ pub fn create_client_endpoint_random_port() -> Option<Endpoint> {
None
}

fn create_server(server_addr: SocketAddr) -> anyhow::Result<Endpoint> {
let cert = Certificate(CERT.to_vec());
let cert_key = PrivateKey(CERT_KEY.to_vec());
let mut server_conf = ServerConfig::with_single_cert(vec![cert.clone()], cert_key)?;
let mut transport = TransportConfig::default();
if std::env::var("AMBIENT_DISABLE_TIMEOUT").is_ok() {
transport.max_idle_timeout(None);
} else {
transport.max_idle_timeout(Some(Duration::from_secs_f32(60.).try_into()?));
}
transport.keep_alive_interval(Some(Duration::from_secs_f32(1.)));
let transport = Arc::new(transport);
server_conf.transport = transport.clone();

let mut endpoint = Endpoint::server(server_conf, server_addr)?;

// Create client config for the server endpoint for proxying and hole punching
let mut roots = RootCertStore::empty();
roots.add(&cert).unwrap();
let crypto = rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(roots)
.with_no_client_auth();
let mut client_config = ClientConfig::new(Arc::new(crypto));
client_config.transport_config(transport);
endpoint.set_default_client_config(client_config);

Ok(endpoint)
}

pub const CERT: &[u8] = include_bytes!("./cert.der");
pub const CERT_KEY: &[u8] = include_bytes!("./cert.key.der");

#[macro_export]
macro_rules! log_network_result {
( $x:expr ) => {
Expand Down
81 changes: 77 additions & 4 deletions crates/network/src/native/server.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
ops::Range,
path::PathBuf,
sync::Arc,
time::Duration,
};
Expand All @@ -21,13 +22,13 @@ use ambient_sys::time::Instant;
use colored::Colorize;
use futures::StreamExt;
use parking_lot::{Mutex, RwLock};
use quinn::Endpoint;
use quinn::{ClientConfig, Endpoint, ServerConfig, TransportConfig};
use rustls::{Certificate, PrivateKey, RootCertStore};
use tokio::time::{interval, MissedTickBehavior};
use uuid::Uuid;

use crate::{
client_connection::ConnectionKind,
create_server,
proto::{
self,
server::{handle_diffs, ConnectionData},
Expand All @@ -40,6 +41,12 @@ use crate::{
stream, ServerWorldExt,
};

#[derive(Debug, Clone)]
pub struct Crypto {
pub cert_file: PathBuf,
pub key_file: PathBuf,
}

/// Quinn and Webtransport game server
pub struct GameServer {
endpoint: Endpoint,
Expand All @@ -53,10 +60,11 @@ impl GameServer {
port: u16,
use_inactivity_shutdown: bool,
proxy_settings: Option<ProxySettings>,
crypto: &Crypto,
) -> anyhow::Result<Self> {
let server_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), port);

let endpoint = create_server(server_addr)?;
let endpoint = create_server(server_addr, crypto)?;

log::debug!("GameServer listening on port {}", port);
Ok(Self {
Expand All @@ -70,9 +78,17 @@ impl GameServer {
port_range: Range<u16>,
use_inactivity_shutdown: bool,
proxy_settings: Option<ProxySettings>,
crypto: &Crypto,
) -> anyhow::Result<Self> {
for port in port_range {
match Self::new_with_port(port, use_inactivity_shutdown, proxy_settings.clone()).await {
match Self::new_with_port(
port,
use_inactivity_shutdown,
proxy_settings.clone(),
crypto,
)
.await
{
Ok(server) => {
return Ok(server);
}
Expand Down Expand Up @@ -401,3 +417,60 @@ async fn start_proxy_connection(
}
}
}

fn create_server(server_addr: SocketAddr, crypto: &Crypto) -> anyhow::Result<Endpoint> {
let cert = Certificate(std::fs::read(&crypto.cert_file)?);
let key = PrivateKey(std::fs::read(&crypto.key_file)?);

let mut tls_config = rustls::ServerConfig::builder()
.with_safe_default_cipher_suites()
.with_safe_default_kx_groups()
.with_protocol_versions(&[&rustls::version::TLS13])
.unwrap()
.with_no_client_auth()
.with_single_cert(vec![cert.clone()], key)?;

tls_config.max_early_data_size = u32::MAX;
let alpn: Vec<Vec<u8>> = vec![
b"h3".to_vec(),
b"h3-32".to_vec(),
b"h3-31".to_vec(),
b"h3-30".to_vec(),
b"h3-29".to_vec(),
b"ambient-02".to_vec(),
];

tls_config.alpn_protocols = alpn;

let mut server_conf = ServerConfig::with_crypto(Arc::new(tls_config));
let mut transport = TransportConfig::default();

transport.keep_alive_interval(Some(Duration::from_secs(2)));

if std::env::var("AMBIENT_DISABLE_TIMEOUT").is_ok() {
transport.max_idle_timeout(None);
} else {
transport.max_idle_timeout(Some(Duration::from_secs_f32(60.).try_into()?));
}

let transport = Arc::new(transport);
server_conf.transport = transport.clone();

tracing::info!(?server_addr, ?server_conf, "Creating server endpoint");

let mut endpoint = Endpoint::server(server_conf, server_addr)?;

// Create client config for the server endpoint for proxying and hole punching
let mut roots = RootCertStore::empty();
roots.add(&cert).unwrap();
let crypto = rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(roots)
.with_no_client_auth();

let mut client_config = ClientConfig::new(Arc::new(crypto));
client_config.transport_config(transport);
endpoint.set_default_client_config(client_config);

Ok(endpoint)
}
Binary file added localhost.crt
Binary file not shown.
Binary file added localhost.key
Binary file not shown.
3 changes: 3 additions & 0 deletions recipes.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,8 @@
"components": {
"cargo-dap": {}
}
},
"launch-chrome": {
"cmd": "./scripts/launch_chrome.sh"
}
}
9 changes: 9 additions & 0 deletions scripts/generate_certs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/sh
# use mkcert to generate certs for localhost
mkcert -key-file localhost-key.pem -cert-file localhost.pem localhost 127.0.0.1 ::1

# convert certs from pem to der
openssl x509 -inform pem -outform der -in localhost.pem -out localhost.crt
openssl rsa -inform pem -outform der -in localhost-key.pem -out localhost.key

rm localhost.pem localhost-key.pem
15 changes: 15 additions & 0 deletions scripts/launch_chrome.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

set -e

SPKI=`openssl x509 -inform der -in localhost.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64`

echo "Got cert key $SPKI"

echo "Opening google chrome"

case `uname` in
(*Linux*) google-chrome --origin-to-force-quic-on=127.0.0.1:4433 --ignore-certificate-errors-spki-list=$SPKI --enable-logging --v=1 &;;
(*Darwin*) open -a "Google Chrome" --args --origin-to-force-quic-on=127.0.0.1:4433 --ignore-certificate-errors-spki-list=$SPKI --enable-logging --v=1 ;;
esac

0 comments on commit caf7122

Please sign in to comment.