diff --git a/gamestream-webtransport-proxy/src/certs.rs b/gamestream-webtransport-proxy/src/certs.rs new file mode 100644 index 0000000..79b7590 --- /dev/null +++ b/gamestream-webtransport-proxy/src/certs.rs @@ -0,0 +1,112 @@ +use std::fs; +use std::io::Write; + +use anyhow::Result; +use openssl::hash::MessageDigest; +use openssl::pkey::{PKey, Private}; +use openssl::rsa::Rsa; +use openssl::x509::X509; + +pub fn get_cert_and_key() -> Result<(X509, PKey)> { + if let Ok((cert, key)) = load_cert_and_key_from_disk() { + Ok((cert, key)) + } else { + generate_cert_and_key() + } +} + +pub fn load_cert_and_key_from_disk() -> Result<(X509, PKey)> { + let project_dirs = + directories::ProjectDirs::from("xyz", "ohea", "gamestream-webtransport-proxy") + .ok_or(anyhow::anyhow!("Could not get project dirs"))?; + let data_dir = project_dirs.data_dir(); + let cert_dir = data_dir.join("certs"); + fs::create_dir_all(&cert_dir)?; + + let cert_filepath = cert_dir.join("cert"); + let key_filepath = cert_dir.join("key"); + + let cert_bytes = fs::read(cert_filepath)?; + let key_bytes = fs::read(key_filepath)?; + + let cert = X509::from_pem(&cert_bytes)?; + let key = PKey::::private_key_from_pem(&key_bytes)?; + + Ok((cert, key)) +} + +pub fn generate_cert_and_key() -> Result<(X509, PKey)> { + let rsa = Rsa::generate(2048)?; + let key = PKey::from_rsa(rsa)?; + + let mut cert_builder = X509::builder()?; + + let serial_number_u32 = openssl::bn::BigNum::from_u32(0)?; + let serial_number = openssl::asn1::Asn1Integer::from_bn(&serial_number_u32)?; + + let now_unix = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("Time went backwards") + .as_secs() + - 1000000; + let now = openssl::asn1::Asn1Time::from_unix(now_unix as i64)?; + let ten_years_from_now = openssl::asn1::Asn1Time::days_from_now(365 * 10)?; + + let mut x509_name_builder = openssl::x509::X509NameBuilder::new()?; + x509_name_builder.append_entry_by_text("CN", "NVIDIA GameStream Client")?; + let x509_name = x509_name_builder.build(); + + cert_builder.set_version(2)?; + cert_builder.set_not_before(&now)?; + cert_builder.set_not_after(&ten_years_from_now)?; + cert_builder.set_pubkey(&key)?; + cert_builder.set_serial_number(&serial_number)?; + cert_builder.set_issuer_name(&x509_name)?; + cert_builder.set_subject_name(&x509_name)?; + cert_builder.sign(&key, MessageDigest::sha256())?; + let cert = cert_builder.build(); + + save_cert_and_key_to_disk(&cert, &key)?; + + Ok((cert, key)) +} + +pub fn save_cert_and_key_to_disk(cert: &X509, key: &PKey) -> Result<()> { + let project_dirs = + directories::ProjectDirs::from("xyz", "ohea", "gamestream-webtransport-proxy") + .ok_or(anyhow::anyhow!("Could not get project dirs"))?; + let data_dir = project_dirs.data_dir(); + let cert_dir = data_dir.join("certs"); + fs::create_dir_all(&cert_dir)?; + + let cert_filepath = cert_dir.join("cert"); + let key_filepath = cert_dir.join("key"); + + let mut cert_file_builder = std::fs::OpenOptions::new(); + cert_file_builder.create(true); + cert_file_builder.truncate(true); + cert_file_builder.write(true); + + let mut key_file_builder = std::fs::OpenOptions::new(); + key_file_builder.create(true); + key_file_builder.truncate(true); + key_file_builder.write(true); + + #[cfg(target_family = "unix")] + { + use std::os::unix::fs::OpenOptionsExt; + + key_file_builder.mode(0o600); + cert_file_builder.mode(0o600); + } + + let mut cert_file = cert_file_builder.open(&cert_filepath)?; + let mut key_file = key_file_builder.open(&key_filepath)?; + + cert_file.write_all(&cert.to_pem()?)?; + key_file.write_all(&key.private_key_to_pem_pkcs8()?)?; + + Ok(()) +} + +pub fn http_client_with_identity() {} diff --git a/gamestream-webtransport-proxy/src/main.rs b/gamestream-webtransport-proxy/src/main.rs index 49685e1..1fac51d 100644 --- a/gamestream-webtransport-proxy/src/main.rs +++ b/gamestream-webtransport-proxy/src/main.rs @@ -7,6 +7,7 @@ use moonlight_common_c_sys::{ STREAM_CFG_LOCAL, VIDEO_FORMAT_H264, }; +mod certs; mod pair; fn get_server_info() -> _SERVER_INFORMATION { diff --git a/gamestream-webtransport-proxy/src/pair.rs b/gamestream-webtransport-proxy/src/pair.rs index c45f17e..b90ea80 100644 --- a/gamestream-webtransport-proxy/src/pair.rs +++ b/gamestream-webtransport-proxy/src/pair.rs @@ -1,14 +1,10 @@ use anyhow::Result; -use openssl::hash::MessageDigest; use openssl::pkey::{PKey, Private}; -use openssl::rsa::Rsa; use openssl::sha::Sha256; use openssl::x509::X509; use rand::Rng; use salvo::prelude::*; use serde::{Deserialize, Serialize}; -use std::fs; -use std::io::Write; #[derive(Debug, Deserialize, ToSchema)] struct PostPairParams { @@ -55,82 +51,6 @@ struct ServerPairingSecret { signature: Vec, } -fn get_cert_and_private_key() -> Result<(X509, PKey)> { - let rsa = Rsa::generate(2048)?; - let key_pair = PKey::from_rsa(rsa)?; - - let mut cert_builder = X509::builder()?; - - let serial_number_u32 = openssl::bn::BigNum::from_u32(0)?; - let serial_number = openssl::asn1::Asn1Integer::from_bn(&serial_number_u32)?; - - let now_unix = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .expect("Time went backwards") - .as_secs() - - 1000000; - let now = openssl::asn1::Asn1Time::from_unix(now_unix as i64)?; - let ten_years_from_now = openssl::asn1::Asn1Time::days_from_now(365 * 10)?; - - let mut x509_name_builder = openssl::x509::X509NameBuilder::new()?; - x509_name_builder.append_entry_by_text("CN", "NVIDIA GameStream Client")?; - let x509_name = x509_name_builder.build(); - - cert_builder.set_version(2)?; - cert_builder.set_not_before(&now)?; - cert_builder.set_not_after(&ten_years_from_now)?; - cert_builder.set_pubkey(&key_pair)?; - cert_builder.set_serial_number(&serial_number)?; - cert_builder.set_issuer_name(&x509_name)?; - cert_builder.set_subject_name(&x509_name)?; - cert_builder.sign(&key_pair, MessageDigest::sha256())?; - let cert = cert_builder.build(); - Ok((cert, key_pair)) -} - -fn save_cert_and_key_to_disk( - cert: X509, - key: PKey, - host: &String, - port: u16, -) -> Result<()> { - let project_dirs = - directories::ProjectDirs::from("xyz", "ohea", "gamestream-webtransport-proxy") - .ok_or(anyhow::anyhow!("Could not get project dirs"))?; - let data_dir = project_dirs.data_dir(); - let cert_dir = data_dir.join("certs"); - fs::create_dir_all(&cert_dir)?; - - let cert_filepath = cert_dir.join(format!("{host}_{port}_cert")); - let key_filepath = cert_dir.join(format!("{host}_{port}_key")); - - let mut cert_file_builder = std::fs::OpenOptions::new(); - cert_file_builder.create(true); - cert_file_builder.truncate(true); - cert_file_builder.write(true); - - let mut key_file_builder = std::fs::OpenOptions::new(); - key_file_builder.create(true); - key_file_builder.truncate(true); - key_file_builder.write(true); - - #[cfg(target_family = "unix")] - { - use std::os::unix::fs::OpenOptionsExt; - - key_file_builder.mode(0o600); - cert_file_builder.mode(0o600); - } - - let mut cert_file = cert_file_builder.open(&cert_filepath)?; - let mut key_file = key_file_builder.open(&key_filepath)?; - - cert_file.write_all(&cert.to_pem()?)?; - key_file.write_all(&key.private_key_to_pem_pkcs8()?)?; - - Ok(()) -} - async fn get_url(base_url: &mut url_constructor::UrlConstructor) -> Result { let mut uuidv2 = [0u8; 16]; openssl::rand::rand_bytes(&mut uuidv2)?; @@ -425,8 +345,8 @@ pub async fn post_pair(body: salvo::oapi::extract::JsonBody) -> return StatusCode::INTERNAL_SERVER_ERROR; } - // Generate certificates - let (cert, private_key) = match get_cert_and_private_key() { + // Get or generate cert / private key + let (cert, private_key) = match crate::certs::get_cert_and_key() { Ok(v) => v, Err(e) => { println!("Could not generate certs: {e}"); @@ -496,11 +416,5 @@ pub async fn post_pair(body: salvo::oapi::extract::JsonBody) -> println!("Paired with server {host}:{port} successfully!"); } - // Save certificate to disk so it can be used for subsequent connections - if let Err(e) = save_cert_and_key_to_disk(cert, private_key, ¶ms.host, params.port) { - println!("Could not save cert and key to disk"); - return StatusCode::INTERNAL_SERVER_ERROR; - }; - StatusCode::OK }