More progress and cleanup

This commit is contained in:
2025-06-27 00:34:46 -06:00
parent 9232c0414a
commit 7ec5e1ad90
+205 -71
View File
@@ -1,19 +1,16 @@
use axum::Json;
use axum::extract::Path;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use openssl::hash::MessageDigest;
use openssl::sha::Sha256;
use openssl::symm::Cipher;
use rand::Rng;
use reqwest::Identity;
use serde::{Deserialize, Serialize};
use openssl::pkey::{PKey, Private};
use openssl::rsa::Rsa;
use openssl::x509::X509;
use openssl::x509::{self, X509};
use anyhow::Result;
use url_constructor::UrlConstructor;
#[derive(Debug, Deserialize)]
struct ServerCertResponse {
@@ -32,63 +29,95 @@ pub struct ServerChallengeResponseResponse {
pairingsecret: String,
}
#[derive(Debug)]
struct ServerCert {
cert: Vec<u8>,
}
#[derive(Debug)]
pub struct ServerPairingSecret {
pairing_secret: Vec<u8>,
signature: Vec<u8>,
}
fn get_cert_and_private_key() -> Result<(X509, PKey<Private>)> {
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))
}
async fn get_url(base_url: &mut url_constructor::UrlConstructor) -> Result<String> {
let mut uuidv2 = [0u8; 16];
openssl::rand::rand_bytes(&mut uuidv2)?;
let uuidv2_hex = hex::encode(uuidv2);
let url = base_url.param("uuid", uuidv2_hex).build();
println!("Getting url: {url}");
let mut http_builder = reqwest::Client::builder();
http_builder = http_builder.user_agent("Mozilla/5.0");
let client = http_builder.build().unwrap();
let resp = client.get(url).send().await?;
let text = resp.text().await?;
Ok(text)
}
async fn get_server_cert(
mut base_url: url_constructor::UrlConstructor,
salt_hex: String,
cert_hex: String,
) -> Result<ServerCertResponse> {
) -> Result<ServerCert> {
// Generate pairing url
let url = base_url
.param("phrase", "getservercert")
.param("salt", &salt_hex)
.param("clientcert", &cert_hex)
.build();
//let url = format!(
// "http://{}/pair?uniqueid={}&uuid={}&devicename=roth&updateState=1&phrase=getservercert&salt={}&clientcert={}",
// host, "0123456789ABCDEF", "23060a17e4fc40f5a83fee298995dd18", salt_hex, cert_hex,
//);
println!("url: {url}");
.param("clientcert", &cert_hex);
//let mut identity_data = private_key.private_key_to_pem_pkcs8().unwrap();
//identity_data.extend_from_slice(&cert_pem);
let text = get_url(url).await?;
let server_cert: ServerCertResponse = serde_xml_rs::from_str(&text)?;
//let identity = Identity::from_pem(&identity_data).unwrap();
//let identity =
// Identity::from_pkcs8_pem(&cert_pem, &private_key.private_key_to_pem_pkcs8().unwrap())
// .unwrap();
let server_cert_bytes = hex::decode(server_cert.plaincert)?;
let mut http_builder = reqwest::Client::builder();
http_builder = http_builder.user_agent("gamestream-webtransport-proxy");
//http_builder = http_builder.identity(identity);
let client = http_builder.build().unwrap();
let resp = client.get(url).send().await?;
let text = resp.text().await.unwrap();
let server_response: ServerCertResponse = serde_xml_rs::from_str(&text).unwrap();
println!("{server_response:?}");
Ok(server_response)
Ok(ServerCert {
cert: server_cert_bytes,
})
}
async fn get_server_challenge(
mut base_url: url_constructor::UrlConstructor,
aes_key: &[u8],
) -> Result<ClientChallengeResponse> {
// 3. Generate random challenge
let mut challenge_data = [0u8; 16];
openssl::rand::rand_bytes(&mut challenge_data)?;
// 4. Encrypt challenge
let mut cipher_ctx = openssl::cipher_ctx::CipherCtx::new()?;
cipher_ctx.encrypt_init(
Some(openssl::cipher::Cipher::aes_128_ecb()),
@@ -101,23 +130,17 @@ async fn get_server_challenge(
cipher_ctx.cipher_update_vec(&challenge_data, &mut challenge_enc)?;
cipher_ctx.cipher_final(&mut challenge_enc)?;
// 5. Convert to hex
let challenge_hex = hex::encode(challenge_enc);
let url = base_url.param("clientchallenge", challenge_hex).build();
let mut http_builder = reqwest::Client::builder();
http_builder = http_builder.user_agent("gamestream-webtransport-proxy");
//http_builder = http_builder.identity(identity);
let client = http_builder.build().unwrap();
let resp = client.get(url).send().await?;
let text = resp.text().await?;
let url = base_url.param("clientchallenge", challenge_hex);
let text = get_url(url).await?;
Ok(serde_xml_rs::from_str(&text)?)
}
fn generate_challenge_response(
client_challenge_response: String,
client_secret_data: &[u8; 16],
aes_key: &[u8],
cert: &X509,
) -> Result<String> {
@@ -145,9 +168,6 @@ fn generate_challenge_response(
)?;
cipher_ctx.cipher_final(&mut client_challenge_response_data)?;
let mut client_secret_data = [0u8; 16];
openssl::rand::rand_bytes(&mut client_secret_data)?;
// Extract ASN.1 signature from certificate
let asn_signature = cert.signature();
let signature_data = asn_signature.as_slice();
@@ -157,7 +177,7 @@ fn generate_challenge_response(
Vec::with_capacity(16 + signature_data.len() + client_secret_data.len());
challenge_response.extend_from_slice(&client_challenge_response_data[32..32 + 16]);
challenge_response.extend_from_slice(signature_data);
challenge_response.extend_from_slice(&client_secret_data);
challenge_response.extend_from_slice(client_secret_data);
let mut hasher = Sha256::new();
hasher.update(&challenge_response);
@@ -184,18 +204,9 @@ async fn send_server_challenge_response(
mut base_url: url_constructor::UrlConstructor,
server_challenge_response: String,
) -> Result<ServerChallengeResponseResponse> {
let url = base_url
.param("serverchallengeresp", server_challenge_response)
.build();
let url = base_url.param("serverchallengeresp", server_challenge_response);
println!("url: {url}");
let mut http_builder = reqwest::Client::builder();
http_builder = http_builder.user_agent("gamestream-webtransport-proxy");
//http_builder = http_builder.identity(identity);
let client = http_builder.build().unwrap();
let resp = client.get(url).send().await?;
let text = resp.text().await?;
let text = get_url(url).await?;
Ok(serde_xml_rs::from_str(&text)?)
}
@@ -211,31 +222,38 @@ async fn generate_aes_key(salt: [u8; 16], pin: [u8; 4]) -> Vec<u8> {
async fn do_challenge(
base_url: url_constructor::UrlConstructor,
client_secret_data: &[u8; 16],
pin: [u8; 4],
salt: [u8; 16],
cert: &X509,
) -> Result<()> {
) -> Result<ServerPairingSecret> {
let aes_key = generate_aes_key(salt, pin).await;
let client_challenge_response = get_server_challenge(base_url.clone(), &aes_key).await?;
println!("{client_challenge_response:?}");
let challenge_response =
generate_challenge_response(client_challenge_response.challengeresponse, &aes_key, cert)?;
println!("{challenge_response:?}");
let challenge_response = generate_challenge_response(
client_challenge_response.challengeresponse,
client_secret_data,
&aes_key,
cert,
)?;
let server_challenge_response_response =
send_server_challenge_response(base_url, challenge_response).await?;
println!("{server_challenge_response_response:?}");
let pairing_secret_bytes = hex::decode(server_challenge_response_response.pairingsecret)?;
Ok(())
Ok(ServerPairingSecret {
pairing_secret: pairing_secret_bytes[0..16].into(),
signature: pairing_secret_bytes[16..].into(),
})
}
// Do a pairing operation
pub async fn get_pair(Path((host, port)): Path<(String, u16)>) -> Response {
let unique_id = "0123456789ABCDEF";
let uuidv2 = "23060a17e4fc40f5a83fee298995dd18";
pub async fn get_base_url(
host: String,
port: u16,
unique_id: String,
) -> url_constructor::UrlConstructor {
let mut base_url = url_constructor::UrlConstructor::new();
base_url
.scheme("http")
@@ -243,14 +261,17 @@ pub async fn get_pair(Path((host, port)): Path<(String, u16)>) -> Response {
.port(port)
.subdir("pair")
.param("uniqueid", unique_id)
.param("uuid", uuidv2)
.param("devicename", "roth")
.param("devicename", "roth") // TODO: what is this roth thing?
.param("updateState", "1");
base_url
}
pub async fn generate_pin() -> [u8; 4] {
let mut pin = [0u8; 4];
{
print!("pairing pin: ");
// TODO: reenable real RNG
let mut rng = rand::rng();
for i in 0..pin.len() {
pin[i] = 5;
@@ -261,6 +282,83 @@ pub async fn get_pair(Path((host, port)): Path<(String, u16)>) -> Response {
// Print as a four-digit, zero-padded integer
println!("");
}
pin
}
pub async fn verify_signature(secret: Vec<u8>, signature: Vec<u8>, cert: Vec<u8>) -> Result<()> {
let cert_x509 = openssl::x509::X509::from_pem(&cert)?;
let cert_pubkey = cert_x509.public_key()?;
let md = openssl::md::Md::sha256();
let mut mdctx = openssl::md_ctx::MdCtx::new()?;
mdctx.digest_verify_init(Some(md), &cert_pubkey)?;
mdctx.digest_verify_update(&secret)?;
match mdctx.digest_verify_final(&signature)? {
true => Ok(()),
false => Err(anyhow::anyhow!(
"Signature failed to verify. Are we being MITMed?"
)),
}
}
pub async fn create_signature(data: &[u8; 16], private_key: &PKey<Private>) -> Result<Vec<u8>> {
let mut signature = Vec::new();
let md = openssl::md::Md::sha256();
let mut mdctx = openssl::md_ctx::MdCtx::new()?;
mdctx.digest_sign_init(Some(md), private_key)?;
mdctx.digest_sign_update(data)?;
mdctx.digest_sign_final_to_vec(&mut signature)?;
Ok(signature)
}
pub async fn send_client_pairing_secret(
mut base_url: url_constructor::UrlConstructor,
client_secret_data: &[u8; 16],
private_key: &PKey<Private>,
) -> Result<()> {
let signature = create_signature(client_secret_data, private_key).await?;
let mut client_secret = Vec::with_capacity(client_secret_data.len() + signature.len());
client_secret.extend_from_slice(client_secret_data);
client_secret.extend_from_slice(&signature);
let client_secret_hex = hex::encode(&client_secret);
let url = base_url.param("clientpairingsecret", client_secret_hex);
let text = get_url(url).await?;
println!("{text:?}");
Ok(())
}
async fn get_unique_id() -> Result<String> {
let mut bytes = [0u8; 8];
openssl::rand::rand_bytes(&mut bytes)?;
Ok(hex::encode(bytes))
}
// Do a pairing operation
pub async fn get_pair(Path((host, port)): Path<(String, u16)>) -> Response {
//let unique_id = "0123456789ABCDEF".to_string();
//let uuidv2 = "23060a17e4fc40f5a83fee298995dd18".to_string();
let unique_id = get_unique_id().await.unwrap();
let base_url = get_base_url(host, port, unique_id).await;
let pin = generate_pin().await;
let mut client_secret_data = [0u8; 16];
if let Err(e) = openssl::rand::rand_bytes(&mut client_secret_data) {
println!("Could not generate client secret data: {e}");
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
}
// Generate certificates
let (cert, private_key) = match get_cert_and_private_key() {
@@ -280,10 +378,46 @@ pub async fn get_pair(Path((host, port)): Path<(String, u16)>) -> Response {
openssl::rand::rand_bytes(&mut salt).unwrap();
let salt_hex = hex::encode(salt);
let server_cert_response = get_server_cert(base_url.clone(), salt_hex, cert_hex).await;
// Get the server certificate and start the pairing process
// This returns once the user has submitted the pin to the
// server out of band.
let server_cert = match get_server_cert(base_url.clone(), salt_hex, cert_hex).await {
Ok(s) => s,
Err(e) => {
println!("could not get server cert: {e}");
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
}
};
println!("{server_cert:?}");
if let Err(e) = do_challenge(base_url.clone(), pin, salt, &cert).await {
println!("could not do challenge: {e}");
// Do the challenge response process
// This returns the pairing secret
let server_pairing_secret =
match do_challenge(base_url.clone(), &client_secret_data, pin, salt, &cert).await {
Ok(s) => s,
Err(e) => {
println!("could not do challenge: {e}");
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
}
};
println!("{server_pairing_secret:?}");
// Verify the pairing_secret signature
if let Err(e) = verify_signature(
server_pairing_secret.pairing_secret,
server_pairing_secret.signature,
server_cert.cert,
)
.await
{
println!("Could not verify signature: {e}");
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
}
if let Err(e) =
send_client_pairing_secret(base_url.clone(), &client_secret_data, &private_key).await
{
println!("Could not send client pairing secret: {e}");
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
};