diff options
Diffstat (limited to 'crates/credential/cargo-credential-wincred')
-rw-r--r-- | crates/credential/cargo-credential-wincred/Cargo.toml | 11 | ||||
-rw-r--r-- | crates/credential/cargo-credential-wincred/src/main.rs | 111 |
2 files changed, 122 insertions, 0 deletions
diff --git a/crates/credential/cargo-credential-wincred/Cargo.toml b/crates/credential/cargo-credential-wincred/Cargo.toml new file mode 100644 index 0000000..83c38e8 --- /dev/null +++ b/crates/credential/cargo-credential-wincred/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "cargo-credential-wincred" +version = "0.2.0" +edition = "2021" +license = "MIT OR Apache-2.0" +repository = "https://github.com/rust-lang/cargo" +description = "A Cargo credential process that stores tokens with Windows Credential Manager." + +[dependencies] +cargo-credential = { version = "0.2.0", path = "../cargo-credential" } +windows-sys = { version = "0.45", features = ["Win32_Foundation", "Win32_Security_Credentials"] } diff --git a/crates/credential/cargo-credential-wincred/src/main.rs b/crates/credential/cargo-credential-wincred/src/main.rs new file mode 100644 index 0000000..8ae48f3 --- /dev/null +++ b/crates/credential/cargo-credential-wincred/src/main.rs @@ -0,0 +1,111 @@ +//! Cargo registry windows credential process. + +use cargo_credential::{Credential, Error}; +use std::ffi::OsStr; +use std::os::windows::ffi::OsStrExt; + +use windows_sys::core::PWSTR; +use windows_sys::Win32::Foundation::ERROR_NOT_FOUND; +use windows_sys::Win32::Foundation::FILETIME; +use windows_sys::Win32::Foundation::TRUE; +use windows_sys::Win32::Security::Credentials::CredDeleteW; +use windows_sys::Win32::Security::Credentials::CredReadW; +use windows_sys::Win32::Security::Credentials::CredWriteW; +use windows_sys::Win32::Security::Credentials::CREDENTIALW; +use windows_sys::Win32::Security::Credentials::CRED_PERSIST_LOCAL_MACHINE; +use windows_sys::Win32::Security::Credentials::CRED_TYPE_GENERIC; + +struct WindowsCredential; + +/// Converts a string to a nul-terminated wide UTF-16 byte sequence. +fn wstr(s: &str) -> Vec<u16> { + let mut wide: Vec<u16> = OsStr::new(s).encode_wide().collect(); + if wide.iter().any(|b| *b == 0) { + panic!("nul byte in wide string"); + } + wide.push(0); + wide +} + +fn target_name(registry_name: &str) -> Vec<u16> { + wstr(&format!("cargo-registry:{}", registry_name)) +} + +impl Credential for WindowsCredential { + fn name(&self) -> &'static str { + env!("CARGO_PKG_NAME") + } + + fn get(&self, index_url: &str) -> Result<String, Error> { + let target_name = target_name(index_url); + let p_credential: *mut CREDENTIALW = std::ptr::null_mut() as *mut _; + unsafe { + if CredReadW( + target_name.as_ptr(), + CRED_TYPE_GENERIC, + 0, + p_credential as *mut _ as *mut _, + ) != TRUE + { + return Err( + format!("failed to fetch token: {}", std::io::Error::last_os_error()).into(), + ); + } + let bytes = std::slice::from_raw_parts( + (*p_credential).CredentialBlob, + (*p_credential).CredentialBlobSize as usize, + ); + String::from_utf8(bytes.to_vec()).map_err(|_| "failed to convert token to UTF8".into()) + } + } + + fn store(&self, index_url: &str, token: &str, name: Option<&str>) -> Result<(), Error> { + let token = token.as_bytes(); + let target_name = target_name(index_url); + let comment = match name { + Some(name) => wstr(&format!("Cargo registry token for {}", name)), + None => wstr("Cargo registry token"), + }; + let mut credential = CREDENTIALW { + Flags: 0, + Type: CRED_TYPE_GENERIC, + TargetName: target_name.as_ptr() as PWSTR, + Comment: comment.as_ptr() as PWSTR, + LastWritten: FILETIME { + dwLowDateTime: 0, + dwHighDateTime: 0, + }, + CredentialBlobSize: token.len() as u32, + CredentialBlob: token.as_ptr() as *mut u8, + Persist: CRED_PERSIST_LOCAL_MACHINE, + AttributeCount: 0, + Attributes: std::ptr::null_mut(), + TargetAlias: std::ptr::null_mut(), + UserName: std::ptr::null_mut(), + }; + let result = unsafe { CredWriteW(&mut credential, 0) }; + if result != TRUE { + let err = std::io::Error::last_os_error(); + return Err(format!("failed to store token: {}", err).into()); + } + Ok(()) + } + + fn erase(&self, index_url: &str) -> Result<(), Error> { + let target_name = target_name(index_url); + let result = unsafe { CredDeleteW(target_name.as_ptr(), CRED_TYPE_GENERIC, 0) }; + if result != TRUE { + let err = std::io::Error::last_os_error(); + if err.raw_os_error() == Some(ERROR_NOT_FOUND as i32) { + eprintln!("not currently logged in to `{}`", index_url); + return Ok(()); + } + return Err(format!("failed to remove token: {}", err).into()); + } + Ok(()) + } +} + +fn main() { + cargo_credential::main(WindowsCredential); +} |