First
This commit is contained in:
164
src/common.rs
Normal file
164
src/common.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
use std::os::windows::ffi::OsStringExt;
|
||||
use windows_sys::Win32::Security::Cryptography;
|
||||
use windows_sys::Win32::System::Registry;
|
||||
|
||||
const DEFAULT_USER_AGENT: &str = "Borland SOAP 1.2";
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StringSystemError { String(std::string::FromUtf8Error), OsString(std::ffi::OsString), SystemError(SystemError) }
|
||||
|
||||
struct ManagedCertificateStore(pub Cryptography::HCERTSTORE);
|
||||
impl Drop for ManagedCertificateStore {
|
||||
fn drop(&mut self) {
|
||||
if self.0 != std::ptr::null_mut() { unsafe { Cryptography::CertCloseStore(self.0, 0); } }
|
||||
}
|
||||
}
|
||||
|
||||
fn get_registry_reg_sz(key: Registry::HKEY, subkey: windows_sys::core::PCWSTR, value: windows_sys::core::PCWSTR) -> Result<String, StringSystemError> {
|
||||
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 }))
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn get_registry_iam() -> String {
|
||||
get_registry_reg_sz(Registry::HKEY_LOCAL_MACHINE, windows_sys::w!("SOFTWARE\\Vista\\Common\\IAM"), std::ptr::null()).unwrap_or_else(|_| "https://services.eauth.va.gov:9301/STS/RequestSecurityToken".to_owned())
|
||||
}
|
||||
pub fn get_registry_iam_ad() -> String {
|
||||
get_registry_reg_sz(Registry::HKEY_LOCAL_MACHINE, windows_sys::w!("SOFTWARE\\Vista\\Common\\IAM_AD"), std::ptr::null()).unwrap_or_else(|_| "https://services.eauth.va.gov:9201/STS/RequestSecurityToken".to_owned())
|
||||
}
|
||||
pub fn get_registry_rioserver() -> String {
|
||||
get_registry_reg_sz(Registry::HKEY_LOCAL_MACHINE, windows_sys::w!("SOFTWARE\\Vista\\Common\\RIOSERVER"), std::ptr::null()).unwrap_or_else(|_| "SecurityTokenService".to_owned())
|
||||
}
|
||||
pub fn get_registry_rioport() -> String {
|
||||
get_registry_reg_sz(Registry::HKEY_LOCAL_MACHINE, windows_sys::w!("SOFTWARE\\Vista\\Common\\RIOPORT"), std::ptr::null()).unwrap_or_else(|_| "RequestSecurityToken".to_owned())
|
||||
}
|
||||
|
||||
pub fn get_iam_request(application: &str, issuer: &str) -> String {
|
||||
format!("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
|
||||
<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:ns=\"http://docs.oasis-open.org/ws-sx/ws-trust/200512\">\
|
||||
<soapenv:Header/>\
|
||||
<soapenv:Body>\
|
||||
<ns:RequestSecurityToken>\
|
||||
<ns:Base>\
|
||||
<wss:TLS xmlns:wss=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\"/>\
|
||||
</ns:Base>\
|
||||
<wsp:AppliesTo xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2004/09/policy\">\
|
||||
<wsa:EndpointReference xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\">\
|
||||
<wsa:Address>{application}</wsa:Address>\
|
||||
</wsa:EndpointReference>\
|
||||
</wsp:AppliesTo>\
|
||||
<ns:Issuer>\
|
||||
<wsa:Address xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\">{issuer}</wsa:Address>\
|
||||
</ns:Issuer>\
|
||||
<ns:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Validate</ns:RequestType>\
|
||||
</ns:RequestSecurityToken>\
|
||||
</soapenv:Body>\
|
||||
</soapenv:Envelope>")
|
||||
}
|
||||
|
||||
pub fn get_local_computer_name() -> Result<String, StringSystemError> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_app_name() -> Option<String> {
|
||||
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> {
|
||||
let void_p_null = std::ptr::null_mut();
|
||||
unsafe {
|
||||
let store_system = ManagedCertificateStore(Cryptography::CertOpenSystemStoreW(0, windows_sys::w!("MY")));
|
||||
let store_memory = ManagedCertificateStore(Cryptography::CertOpenStore(Cryptography::CERT_STORE_PROV_MEMORY, 0, 0, 0, void_p_null));
|
||||
let mut cert_selection = void_p_null as *const Cryptography::CERT_CONTEXT;
|
||||
let mut cert_iter = void_p_null as *const Cryptography::CERT_CONTEXT;
|
||||
while { cert_iter = Cryptography::CertEnumCertificatesInStore(store_system.0, cert_iter); cert_iter } != void_p_null as *const Cryptography::CERT_CONTEXT {
|
||||
let mut cert_valid = Cryptography::CertFindCertificateInStore(store_system.0, Cryptography::X509_ASN_ENCODING | Cryptography::PKCS_7_ASN_ENCODING, 0, Cryptography::CERT_FIND_ANY, void_p_null, void_p_null as *const Cryptography::CERT_CONTEXT);
|
||||
if cert_valid != void_p_null as *mut Cryptography::CERT_CONTEXT {
|
||||
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 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);
|
||||
if ((key_usage_bits as u32 & Cryptography::CERT_DIGITAL_SIGNATURE_KEY_USAGE) == Cryptography::CERT_DIGITAL_SIGNATURE_KEY_USAGE) && (valid_date == 0) && (!name_string.contains("Card Authentication")) && (!name_string.contains("0,")) && (!name_string.contains("Signature")) {
|
||||
Cryptography::CertAddCertificateContextToStore(store_memory.0, cert_iter, Cryptography::CERT_STORE_ADD_ALWAYS, &mut cert_valid);
|
||||
cert_selection = cert_valid;
|
||||
}
|
||||
}
|
||||
}
|
||||
//Cryptography::CertCloseStore(store_system.0, 0);
|
||||
if show_cert_dialog {
|
||||
cert_selection = Cryptography::UI::CryptUIDlgSelectCertificateFromStore(store_memory.0, 0, 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 })) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_certificate_thumbprint(certificate: *const Cryptography::CERT_CONTEXT) -> String {
|
||||
let mut bufsz: u32 = 0;
|
||||
unsafe { Cryptography::CertGetCertificateContextProperty(certificate, Cryptography::CERT_HASH_PROP_ID, std::ptr::null_mut(), &mut bufsz); }
|
||||
let mut buffer = vec![0 as u8; bufsz as usize];
|
||||
unsafe { Cryptography::CertGetCertificateContextProperty(certificate, Cryptography::CERT_HASH_PROP_ID, buffer.as_mut_ptr() as *mut std::ffi::c_void, &mut bufsz); }
|
||||
buffer.iter().map(|b| format!("{:02x}", b)).collect::<Vec<String>>().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<String, StringSystemError> {
|
||||
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))
|
||||
}
|
2
src/lib.rs
Normal file
2
src/lib.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod common;
|
||||
pub use common::*;
|
10
src/main.rs
Normal file
10
src/main.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
mod common;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
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) },
|
||||
_ => { std::process::exit(1) }
|
||||
};
|
||||
Ok(())
|
||||
}
|
Reference in New Issue
Block a user