More progress and cleanup
This commit is contained in:
@@ -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,11 +378,47 @@ 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 {
|
||||
// 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();
|
||||
};
|
||||
|
||||
"test".into_response()
|
||||
|
||||
Reference in New Issue
Block a user