From 7558da890d3ff22203f727b6e2bd4fc4fad3b732 Mon Sep 17 00:00:00 2001 From: inportb Date: Tue, 4 Nov 2025 17:34:06 -0500 Subject: [PATCH] Use libcurl instead of curl --- Cargo.toml | 3 +- src/common.rs | 137 +++++++++++++++++++++++++++++++++----------------- src/main.rs | 2 +- 3 files changed, 95 insertions(+), 47 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a2b1993..b63dabe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "XWBSSOi" -version = "0.0.1" +version = "0.0.2" edition = "2018" [dependencies] @@ -12,6 +12,7 @@ windows-sys = { version = "*", features = [ "Win32_System_Registry", "Win32_System_SystemInformation", ] } +curl = "*" [profile.release] codegen-units = 1 diff --git a/src/common.rs b/src/common.rs index 47969f0..8fac4ab 100644 --- a/src/common.rs +++ b/src/common.rs @@ -7,16 +7,70 @@ const DEFAULT_COMPUTER_NAME: &str = "localhost"; const DEFAULT_APP_NAME: &str = "CPRSChart.exe"; const DEFAULT_ISSUER: &str = "https://ssoi.sts.va.gov/Issuer/smtoken/SAML2"; -#[derive(Debug)] -pub struct SystemError { pub code: windows_sys::Win32::Foundation::WIN32_ERROR } -impl std::fmt::Display for SystemError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "System error: {:x}", self.code) +pub struct TokenConfig { + pub iam: String, + pub ua: String, + pub certificate: String, + pub issuer: String, + pub hostname: String, + pub application: String, +} + +impl Default for TokenConfig { + fn default() -> Self { + Self { + iam: String::new(), + ua: String::new(), + certificate: String::new(), + issuer: String::new(), + hostname: String::new(), + application: String::new(), + } } } #[derive(Debug)] -pub enum StringSystemError { String(std::string::FromUtf8Error), OsString(std::ffi::OsString), SystemError(SystemError) } +pub struct SystemError { pub code: windows_sys::Win32::Foundation::WIN32_ERROR } +impl std::fmt::Display for SystemError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "SystemError: {:x}", self.code) + } +} +impl std::error::Error for SystemError {} + +#[derive(Debug)] +pub enum SSOError { + SystemError(SystemError), + FromUtf8Error(std::string::FromUtf8Error), + OsString(std::ffi::OsString), + CurlError(curl::Error), +} +impl std::fmt::Display for SSOError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "SSOError: {:?}", self) + } +} +impl std::error::Error for SSOError {} +impl From for SSOError { + fn from(err: SystemError) -> Self { + SSOError::SystemError(err) + } +} +impl From for SSOError { + fn from(err: std::string::FromUtf8Error) -> Self { + SSOError::FromUtf8Error(err) + } +} +impl From for SSOError { + fn from(err: std::ffi::OsString) -> Self { + SSOError::OsString(err) + } +} +impl From for SSOError { + fn from(err: curl::Error) -> Self { + SSOError::CurlError(err) + } +} struct ManagedCertificateStore(pub Cryptography::HCERTSTORE); impl Drop for ManagedCertificateStore { @@ -25,15 +79,15 @@ impl Drop for ManagedCertificateStore { } } -fn get_registry_reg_sz(key: Registry::HKEY, subkey: windows_sys::core::PCWSTR, value: windows_sys::core::PCWSTR) -> Result { +fn get_registry_reg_sz(key: Registry::HKEY, subkey: windows_sys::core::PCWSTR, value: windows_sys::core::PCWSTR) -> Result { let mut bufsz: u32 = 0; unsafe { Registry::RegGetValueW(key, subkey, value, Registry::RRF_RT_REG_SZ, std::ptr::null_mut(), std::ptr::null_mut(), &mut bufsz); } loop { let mut buffer = vec![0 as u16; bufsz as usize >> 1]; match unsafe { Registry::RegGetValueW(key, subkey, value, Registry::RRF_RT_REG_SZ, std::ptr::null_mut(), buffer.as_mut_ptr() as *mut std::ffi::c_void, &mut bufsz) } { windows_sys::Win32::Foundation::ERROR_MORE_DATA => continue, - windows_sys::Win32::Foundation::ERROR_SUCCESS => return Ok(std::ffi::OsString::from_wide(&buffer[0 .. (bufsz as usize >> 1) - 1]).into_string().map_err(|err| StringSystemError::OsString(err))?), - value => return Err(StringSystemError::SystemError(SystemError { code: value })) + windows_sys::Win32::Foundation::ERROR_SUCCESS => return Ok(std::ffi::OsString::from_wide(&buffer[0 .. (bufsz as usize >> 1) - 1]).into_string()?), + value => return Err(SSOError::SystemError(SystemError { code: value })), } } } @@ -73,14 +127,14 @@ pub fn get_iam_request(application: &str, issuer: &str) -> String { ") } -pub fn get_local_computer_name() -> Result { +pub fn get_local_computer_name() -> Result { use windows_sys::Win32::System::SystemInformation; let mut bufsz: u32 = 0; unsafe { SystemInformation::GetComputerNameExW(SystemInformation::ComputerNameDnsFullyQualified, std::ptr::null_mut() as windows_sys::core::PWSTR, &mut bufsz); } let mut buffer = vec![0 as u16; bufsz as usize]; match unsafe { SystemInformation::GetComputerNameExW(SystemInformation::ComputerNameDnsFullyQualified, buffer.as_mut_ptr() as windows_sys::core::PWSTR, &mut bufsz) } { - 0 => unsafe { Err(StringSystemError::SystemError(SystemError { code: windows_sys::Win32::Foundation::GetLastError() })) }, - _ => std::ffi::OsString::from_wide(&buffer[0 .. bufsz as usize]).into_string().map_err(|err| StringSystemError::OsString(err)) + 0 => unsafe { Err(SSOError::SystemError(SystemError { code: windows_sys::Win32::Foundation::GetLastError() })) }, + _ => Ok(std::ffi::OsString::from_wide(&buffer[0 .. bufsz as usize]).into_string()?), } } @@ -88,7 +142,7 @@ pub fn get_app_name() -> Option { std::env::current_exe().ok()?.file_name()?.to_str()?.to_owned().into() } -pub fn get_vista_certificate(show_cert_dialog: bool) -> Result<*const Cryptography::CERT_CONTEXT, StringSystemError> { +pub fn get_vista_certificate(show_cert_dialog: bool) -> Result<*const Cryptography::CERT_CONTEXT, SSOError> { let void_p_null = std::ptr::null_mut(); unsafe { let store_system = ManagedCertificateStore(Cryptography::CertOpenSystemStoreW(0, windows_sys::w!("MY"))); @@ -101,7 +155,7 @@ pub fn get_vista_certificate(show_cert_dialog: bool) -> Result<*const Cryptograp let name_bufsz = Cryptography::CertGetNameStringW(cert_iter, Cryptography::CERT_NAME_FRIENDLY_DISPLAY_TYPE, 0, void_p_null, void_p_null as windows_sys::core::PWSTR, 0); let mut name_buffer = vec![0 as u16; name_bufsz as usize]; Cryptography::CertGetNameStringW(cert_iter, Cryptography::CERT_NAME_FRIENDLY_DISPLAY_TYPE, 0, void_p_null, name_buffer.as_mut_ptr() as windows_sys::core::PWSTR, name_bufsz); - let name_string = std::ffi::OsString::from_wide(&name_buffer[0 .. name_bufsz as usize - 1]).into_string().map_err(|err| StringSystemError::OsString(err))?; + let name_string = std::ffi::OsString::from_wide(&name_buffer[0 .. name_bufsz as usize - 1]).into_string()?; let mut key_usage_bits: u8 = 0; Cryptography::CertGetIntendedKeyUsage(Cryptography::X509_ASN_ENCODING | Cryptography::PKCS_7_ASN_ENCODING, (*cert_iter).pCertInfo, &mut key_usage_bits, std::mem::size_of_val(&key_usage_bits) as u32); let valid_date = Cryptography::CertVerifyTimeValidity(void_p_null as *const windows_sys::Win32::Foundation::FILETIME, (*cert_iter).pCertInfo); @@ -116,7 +170,7 @@ pub fn get_vista_certificate(show_cert_dialog: bool) -> Result<*const Cryptograp cert_selection = Cryptography::UI::CryptUIDlgSelectCertificateFromStore(store_memory.0, void_p_null, windows_sys::w!("VistA Logon - Certificate Selection"), windows_sys::w!("Select a certificate for VistA authentication"), 0, 0, void_p_null); } //Cryptography::CertCloseStore(store_memory.0, 0); - if cert_selection != void_p_null as *const Cryptography::CERT_CONTEXT { Ok(cert_selection) } else { Err(StringSystemError::SystemError(SystemError { code: windows_sys::Win32::Foundation::ERROR_REQUEST_REFUSED })) } + if cert_selection != void_p_null as *const Cryptography::CERT_CONTEXT { Ok(cert_selection) } else { Err(SSOError::SystemError(SystemError { code: windows_sys::Win32::Foundation::ERROR_REQUEST_REFUSED })) } } } @@ -128,37 +182,30 @@ pub fn get_certificate_thumbprint(certificate: *const Cryptography::CERT_CONTEXT buffer.iter().map(|b| format!("{:02x}", b)).collect::>().join("") } -pub struct TokenConfig { - pub iam: String, - pub ua: String, - pub certificate: String, - pub issuer: String, - pub hostname: String, - pub application: String, -} - -impl Default for TokenConfig { - fn default() -> Self { - Self { - iam: String::new(), - ua: String::new(), - certificate: String::new(), - issuer: String::new(), - hostname: String::new(), - application: String::new(), - } - } -} - -pub fn get_sso_token(config: TokenConfig) -> Result { +pub fn get_sso_token(config: TokenConfig) -> Result { let config_certificate = match config.certificate.as_str() { "" => get_certificate_thumbprint(get_vista_certificate(true)?), _ => config.certificate }; let config_hostname = match config.hostname.as_str() { "" => get_local_computer_name().unwrap_or_else(|_| DEFAULT_COMPUTER_NAME.to_owned()), _ => config.hostname }; let config_application = match config.application.as_str() { "" => get_app_name().unwrap_or_else(|| DEFAULT_APP_NAME.to_owned()), _ => config.application }; - let mut req = std::process::Command::new("curl"); - req.arg("-fsSL").arg("-X").arg("POST").arg(match config.iam.as_str() { "" => get_registry_iam(), _ => config.iam }) - .arg("--ca-native").arg("--cert").arg(format!("CurrentUser\\MY\\{config_certificate}")) - .arg("-A").arg(match config.ua.as_str() { "" => DEFAULT_USER_AGENT, v => v }) - .arg("-H").arg("Content-Type: application/xml").arg("-H").arg("Accept: application/xml") - .arg("-d").arg(get_iam_request(format!("https://{config_hostname}/Delphi_RPC_Broker/{config_application}").as_str(), match config.issuer.as_str() { "" => DEFAULT_ISSUER, v => v })); - String::from_utf8(req.stdin(std::process::Stdio::null()).stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::inherit()).output().unwrap().stdout).map_err(|err| StringSystemError::String(err)) + let config_iam = match config.iam.as_str() { "" => get_registry_iam(), _ => config.iam }; + let mut easy = curl::easy::Easy::new(); + easy.url(&config_iam)?; + easy.ssl_cert(format!("CurrentUser\\MY\\{config_certificate}"))?; + easy.ssl_options(curl::easy::SslOpt::new().native_ca(true))?; + let mut headers = curl::easy::List::new(); + headers.append("Content-Type: application/xml")?; + headers.append("Accept: application/xml")?; + easy.http_headers(headers)?; + easy.useragent(match config.ua.as_str() { "" => DEFAULT_USER_AGENT, v => v })?; + easy.post(true)?; + let request_body = get_iam_request(format!("https://{config_hostname}/Delphi_RPC_Broker/{config_application}").as_str(), match config.issuer.as_str() { "" => DEFAULT_ISSUER, v => v }); + easy.post_field_size(request_body.len() as u64)?; + let mut response_body = Vec::new(); + { + use std::io::Read; + let mut transfer = easy.transfer(); + transfer.read_function(|buf| Ok(request_body.as_bytes().read(buf).unwrap_or(0)))?; + transfer.write_function(|buf| { response_body.extend_from_slice(buf); Ok(buf.len()) })?; + transfer.perform()?; + } + return Ok(String::from_utf8(response_body)?); } diff --git a/src/main.rs b/src/main.rs index 0f658e4..64b316c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ mod common; fn main() -> Result<(), Box> { match common::get_sso_token(common::TokenConfig::default()) { Ok(value) => println!("{:?}", value), - Err(common::StringSystemError::SystemError(common::SystemError { code })) => { std::process::exit(code as i32) }, + Err(common::SSOError::SystemError(common::SystemError { code })) => { std::process::exit(code as i32) }, _ => { std::process::exit(1) } }; Ok(())