1 use log::{debug, trace}; 2 use std::ffi::CString; 3 use std::io::Write; 4 use std::mem; 5 use std::path::Path; 6 use std::process::{Command, Stdio}; 7 use std::ptr; 8 use url; 9 10 use crate::util::Binding; 11 use crate::{raw, Config, Error, IntoCString}; 12 13 /// A structure to represent git credentials in libgit2. 14 pub struct Cred { 15 raw: *mut raw::git_cred, 16 } 17 18 /// Management of the gitcredentials(7) interface. 19 pub struct CredentialHelper { 20 /// A public field representing the currently discovered username from 21 /// configuration. 22 pub username: Option<String>, 23 protocol: Option<String>, 24 host: Option<String>, 25 url: String, 26 commands: Vec<String>, 27 } 28 29 impl Cred { 30 /// Create a "default" credential usable for Negotiate mechanisms like NTLM 31 /// or Kerberos authentication. default() -> Result<Cred, Error>32 pub fn default() -> Result<Cred, Error> { 33 crate::init(); 34 let mut out = ptr::null_mut(); 35 unsafe { 36 try_call!(raw::git_cred_default_new(&mut out)); 37 Ok(Binding::from_raw(out)) 38 } 39 } 40 41 /// Create a new ssh key credential object used for querying an ssh-agent. 42 /// 43 /// The username specified is the username to authenticate. ssh_key_from_agent(username: &str) -> Result<Cred, Error>44 pub fn ssh_key_from_agent(username: &str) -> Result<Cred, Error> { 45 crate::init(); 46 let mut out = ptr::null_mut(); 47 let username = CString::new(username)?; 48 unsafe { 49 try_call!(raw::git_cred_ssh_key_from_agent(&mut out, username)); 50 Ok(Binding::from_raw(out)) 51 } 52 } 53 54 /// Create a new passphrase-protected ssh key credential object. ssh_key( username: &str, publickey: Option<&Path>, privatekey: &Path, passphrase: Option<&str>, ) -> Result<Cred, Error>55 pub fn ssh_key( 56 username: &str, 57 publickey: Option<&Path>, 58 privatekey: &Path, 59 passphrase: Option<&str>, 60 ) -> Result<Cred, Error> { 61 crate::init(); 62 let username = CString::new(username)?; 63 let publickey = crate::opt_cstr(publickey)?; 64 let privatekey = privatekey.into_c_string()?; 65 let passphrase = crate::opt_cstr(passphrase)?; 66 let mut out = ptr::null_mut(); 67 unsafe { 68 try_call!(raw::git_cred_ssh_key_new( 69 &mut out, username, publickey, privatekey, passphrase 70 )); 71 Ok(Binding::from_raw(out)) 72 } 73 } 74 75 /// Create a new ssh key credential object reading the keys from memory. ssh_key_from_memory( username: &str, publickey: Option<&str>, privatekey: &str, passphrase: Option<&str>, ) -> Result<Cred, Error>76 pub fn ssh_key_from_memory( 77 username: &str, 78 publickey: Option<&str>, 79 privatekey: &str, 80 passphrase: Option<&str>, 81 ) -> Result<Cred, Error> { 82 crate::init(); 83 let username = CString::new(username)?; 84 let publickey = crate::opt_cstr(publickey)?; 85 let privatekey = CString::new(privatekey)?; 86 let passphrase = crate::opt_cstr(passphrase)?; 87 let mut out = ptr::null_mut(); 88 unsafe { 89 try_call!(raw::git_cred_ssh_key_memory_new( 90 &mut out, username, publickey, privatekey, passphrase 91 )); 92 Ok(Binding::from_raw(out)) 93 } 94 } 95 96 /// Create a new plain-text username and password credential object. userpass_plaintext(username: &str, password: &str) -> Result<Cred, Error>97 pub fn userpass_plaintext(username: &str, password: &str) -> Result<Cred, Error> { 98 crate::init(); 99 let username = CString::new(username)?; 100 let password = CString::new(password)?; 101 let mut out = ptr::null_mut(); 102 unsafe { 103 try_call!(raw::git_cred_userpass_plaintext_new( 104 &mut out, username, password 105 )); 106 Ok(Binding::from_raw(out)) 107 } 108 } 109 110 /// Attempt to read `credential.helper` according to gitcredentials(7) [1] 111 /// 112 /// This function will attempt to parse the user's `credential.helper` 113 /// configuration, invoke the necessary processes, and read off what the 114 /// username/password should be for a particular url. 115 /// 116 /// The returned credential type will be a username/password credential if 117 /// successful. 118 /// 119 /// [1]: https://www.kernel.org/pub/software/scm/git/docs/gitcredentials.html credential_helper( config: &Config, url: &str, username: Option<&str>, ) -> Result<Cred, Error>120 pub fn credential_helper( 121 config: &Config, 122 url: &str, 123 username: Option<&str>, 124 ) -> Result<Cred, Error> { 125 match CredentialHelper::new(url) 126 .config(config) 127 .username(username) 128 .execute() 129 { 130 Some((username, password)) => Cred::userpass_plaintext(&username, &password), 131 None => Err(Error::from_str( 132 "failed to acquire username/password \ 133 from local configuration", 134 )), 135 } 136 } 137 138 /// Create a credential to specify a username. 139 /// 140 /// This is used with ssh authentication to query for the username if none is 141 /// specified in the url. username(username: &str) -> Result<Cred, Error>142 pub fn username(username: &str) -> Result<Cred, Error> { 143 crate::init(); 144 let username = CString::new(username)?; 145 let mut out = ptr::null_mut(); 146 unsafe { 147 try_call!(raw::git_cred_username_new(&mut out, username)); 148 Ok(Binding::from_raw(out)) 149 } 150 } 151 152 /// Check whether a credential object contains username information. has_username(&self) -> bool153 pub fn has_username(&self) -> bool { 154 unsafe { raw::git_cred_has_username(self.raw) == 1 } 155 } 156 157 /// Return the type of credentials that this object represents. credtype(&self) -> raw::git_credtype_t158 pub fn credtype(&self) -> raw::git_credtype_t { 159 unsafe { (*self.raw).credtype } 160 } 161 162 /// Unwrap access to the underlying raw pointer, canceling the destructor unwrap(mut self) -> *mut raw::git_cred163 pub unsafe fn unwrap(mut self) -> *mut raw::git_cred { 164 mem::replace(&mut self.raw, ptr::null_mut()) 165 } 166 } 167 168 impl Binding for Cred { 169 type Raw = *mut raw::git_cred; 170 from_raw(raw: *mut raw::git_cred) -> Cred171 unsafe fn from_raw(raw: *mut raw::git_cred) -> Cred { 172 Cred { raw: raw } 173 } raw(&self) -> *mut raw::git_cred174 fn raw(&self) -> *mut raw::git_cred { 175 self.raw 176 } 177 } 178 179 impl Drop for Cred { drop(&mut self)180 fn drop(&mut self) { 181 if !self.raw.is_null() { 182 unsafe { 183 if let Some(f) = (*self.raw).free { 184 f(self.raw) 185 } 186 } 187 } 188 } 189 } 190 191 impl CredentialHelper { 192 /// Create a new credential helper object which will be used to probe git's 193 /// local credential configuration. 194 /// 195 /// The url specified is the namespace on which this will query credentials. 196 /// Invalid urls are currently ignored. new(url: &str) -> CredentialHelper197 pub fn new(url: &str) -> CredentialHelper { 198 let mut ret = CredentialHelper { 199 protocol: None, 200 host: None, 201 username: None, 202 url: url.to_string(), 203 commands: Vec::new(), 204 }; 205 206 // Parse out the (protocol, host) if one is available 207 if let Ok(url) = url::Url::parse(url) { 208 if let Some(url::Host::Domain(s)) = url.host() { 209 ret.host = Some(s.to_string()); 210 } 211 ret.protocol = Some(url.scheme().to_string()) 212 } 213 ret 214 } 215 216 /// Set the username that this credential helper will query with. 217 /// 218 /// By default the username is `None`. username(&mut self, username: Option<&str>) -> &mut CredentialHelper219 pub fn username(&mut self, username: Option<&str>) -> &mut CredentialHelper { 220 self.username = username.map(|s| s.to_string()); 221 self 222 } 223 224 /// Query the specified configuration object to discover commands to 225 /// execute, usernames to query, etc. config(&mut self, config: &Config) -> &mut CredentialHelper226 pub fn config(&mut self, config: &Config) -> &mut CredentialHelper { 227 // Figure out the configured username/helper program. 228 // 229 // see http://git-scm.com/docs/gitcredentials.html#_configuration_options 230 // 231 // TODO: implement useHttpPath 232 if self.username.is_none() { 233 self.config_username(config); 234 } 235 self.config_helper(config); 236 self 237 } 238 239 // Configure the queried username from `config` config_username(&mut self, config: &Config)240 fn config_username(&mut self, config: &Config) { 241 let key = self.exact_key("username"); 242 self.username = config 243 .get_string(&key) 244 .ok() 245 .or_else(|| { 246 self.url_key("username") 247 .and_then(|s| config.get_string(&s).ok()) 248 }) 249 .or_else(|| config.get_string("credential.username").ok()) 250 } 251 252 // Discover all `helper` directives from `config` config_helper(&mut self, config: &Config)253 fn config_helper(&mut self, config: &Config) { 254 let exact = config.get_string(&self.exact_key("helper")); 255 self.add_command(exact.as_ref().ok().map(|s| &s[..])); 256 if let Some(key) = self.url_key("helper") { 257 let url = config.get_string(&key); 258 self.add_command(url.as_ref().ok().map(|s| &s[..])); 259 } 260 let global = config.get_string("credential.helper"); 261 self.add_command(global.as_ref().ok().map(|s| &s[..])); 262 } 263 264 // Add a `helper` configured command to the list of commands to execute. 265 // 266 // see https://www.kernel.org/pub/software/scm/git/docs/technical 267 // /api-credentials.html#_credential_helpers add_command(&mut self, cmd: Option<&str>)268 fn add_command(&mut self, cmd: Option<&str>) { 269 let cmd = match cmd { 270 Some("") | None => return, 271 Some(s) => s, 272 }; 273 274 if cmd.starts_with('!') { 275 self.commands.push(cmd[1..].to_string()); 276 } else if cmd.contains("/") || cmd.contains("\\") { 277 self.commands.push(cmd.to_string()); 278 } else { 279 self.commands.push(format!("git credential-{}", cmd)); 280 } 281 } 282 283 fn exact_key(&self, name: &str) -> String { 284 format!("credential.{}.{}", self.url, name) 285 } 286 287 fn url_key(&self, name: &str) -> Option<String> { 288 match (&self.host, &self.protocol) { 289 (&Some(ref host), &Some(ref protocol)) => { 290 Some(format!("credential.{}://{}.{}", protocol, host, name)) 291 } 292 _ => None, 293 } 294 } 295 296 /// Execute this helper, attempting to discover a username/password pair. 297 /// 298 /// All I/O errors are ignored, (to match git behavior), and this function 299 /// only succeeds if both a username and a password were found execute(&self) -> Option<(String, String)>300 pub fn execute(&self) -> Option<(String, String)> { 301 let mut username = self.username.clone(); 302 let mut password = None; 303 for cmd in &self.commands { 304 let (u, p) = self.execute_cmd(cmd, &username); 305 if u.is_some() && username.is_none() { 306 username = u; 307 } 308 if p.is_some() && password.is_none() { 309 password = p; 310 } 311 if username.is_some() && password.is_some() { 312 break; 313 } 314 } 315 316 match (username, password) { 317 (Some(u), Some(p)) => Some((u, p)), 318 _ => None, 319 } 320 } 321 322 // Execute the given `cmd`, providing the appropriate variables on stdin and 323 // then afterwards parsing the output into the username/password on stdout. execute_cmd( &self, cmd: &str, username: &Option<String>, ) -> (Option<String>, Option<String>)324 fn execute_cmd( 325 &self, 326 cmd: &str, 327 username: &Option<String>, 328 ) -> (Option<String>, Option<String>) { 329 macro_rules! my_try( ($e:expr) => ( 330 match $e { 331 Ok(e) => e, 332 Err(e) => { 333 debug!("{} failed with {}", stringify!($e), e); 334 return (None, None) 335 } 336 } 337 ) ); 338 339 // It looks like the `cmd` specification is typically bourne-shell-like 340 // syntax, so try that first. If that fails, though, we may be on a 341 // Windows machine for example where `sh` isn't actually available by 342 // default. Most credential helper configurations though are pretty 343 // simple (aka one or two space-separated strings) so also try to invoke 344 // the process directly. 345 // 346 // If that fails then it's up to the user to put `sh` in path and make 347 // sure it works. 348 let mut c = Command::new("sh"); 349 c.arg("-c") 350 .arg(&format!("{} get", cmd)) 351 .stdin(Stdio::piped()) 352 .stdout(Stdio::piped()) 353 .stderr(Stdio::piped()); 354 debug!("executing credential helper {:?}", c); 355 let mut p = match c.spawn() { 356 Ok(p) => p, 357 Err(e) => { 358 debug!("`sh` failed to spawn: {}", e); 359 let mut parts = cmd.split_whitespace(); 360 let mut c = Command::new(parts.next().unwrap()); 361 for arg in parts { 362 c.arg(arg); 363 } 364 c.arg("get") 365 .stdin(Stdio::piped()) 366 .stdout(Stdio::piped()) 367 .stderr(Stdio::piped()); 368 debug!("executing credential helper {:?}", c); 369 match c.spawn() { 370 Ok(p) => p, 371 Err(e) => { 372 debug!("fallback of {:?} failed with {}", cmd, e); 373 return (None, None); 374 } 375 } 376 } 377 }; 378 379 // Ignore write errors as the command may not actually be listening for 380 // stdin 381 { 382 let stdin = p.stdin.as_mut().unwrap(); 383 if let Some(ref p) = self.protocol { 384 let _ = writeln!(stdin, "protocol={}", p); 385 } 386 if let Some(ref p) = self.host { 387 let _ = writeln!(stdin, "host={}", p); 388 } 389 if let Some(ref p) = *username { 390 let _ = writeln!(stdin, "username={}", p); 391 } 392 } 393 let output = my_try!(p.wait_with_output()); 394 if !output.status.success() { 395 debug!( 396 "credential helper failed: {}\nstdout ---\n{}\nstdout ---\n{}", 397 output.status, 398 String::from_utf8_lossy(&output.stdout), 399 String::from_utf8_lossy(&output.stderr) 400 ); 401 return (None, None); 402 } 403 trace!( 404 "credential helper stderr ---\n{}", 405 String::from_utf8_lossy(&output.stderr) 406 ); 407 self.parse_output(output.stdout) 408 } 409 410 // Parse the output of a command into the username/password found parse_output(&self, output: Vec<u8>) -> (Option<String>, Option<String>)411 fn parse_output(&self, output: Vec<u8>) -> (Option<String>, Option<String>) { 412 // Parse the output of the command, looking for username/password 413 let mut username = None; 414 let mut password = None; 415 for line in output.split(|t| *t == b'\n') { 416 let mut parts = line.splitn(2, |t| *t == b'='); 417 let key = parts.next().unwrap(); 418 let value = match parts.next() { 419 Some(s) => s, 420 None => { 421 trace!("ignoring output line: {}", String::from_utf8_lossy(line)); 422 continue; 423 } 424 }; 425 let value = match String::from_utf8(value.to_vec()) { 426 Ok(s) => s, 427 Err(..) => continue, 428 }; 429 match key { 430 b"username" => username = Some(value), 431 b"password" => password = Some(value), 432 _ => {} 433 } 434 } 435 (username, password) 436 } 437 } 438 439 #[cfg(test)] 440 mod test { 441 use std::env; 442 use std::fs::File; 443 use std::io::prelude::*; 444 use std::path::Path; 445 use tempfile::TempDir; 446 447 use crate::{Config, ConfigLevel, Cred, CredentialHelper}; 448 449 macro_rules! test_cfg( ($($k:expr => $v:expr),*) => ({ 450 let td = TempDir::new().unwrap(); 451 let mut cfg = Config::new().unwrap(); 452 cfg.add_file(&td.path().join("cfg"), ConfigLevel::Highest, false).unwrap(); 453 $(cfg.set_str($k, $v).unwrap();)* 454 cfg 455 }) ); 456 457 #[test] smoke()458 fn smoke() { 459 Cred::default().unwrap(); 460 } 461 462 #[test] credential_helper1()463 fn credential_helper1() { 464 let cfg = test_cfg! { 465 "credential.helper" => "!f() { echo username=a; echo password=b; }; f" 466 }; 467 let (u, p) = CredentialHelper::new("https://example.com/foo/bar") 468 .config(&cfg) 469 .execute() 470 .unwrap(); 471 assert_eq!(u, "a"); 472 assert_eq!(p, "b"); 473 } 474 475 #[test] credential_helper2()476 fn credential_helper2() { 477 let cfg = test_cfg! {}; 478 assert!(CredentialHelper::new("https://example.com/foo/bar") 479 .config(&cfg) 480 .execute() 481 .is_none()); 482 } 483 484 #[test] credential_helper3()485 fn credential_helper3() { 486 let cfg = test_cfg! { 487 "credential.https://example.com.helper" => 488 "!f() { echo username=c; }; f", 489 "credential.helper" => "!f() { echo username=a; echo password=b; }; f" 490 }; 491 let (u, p) = CredentialHelper::new("https://example.com/foo/bar") 492 .config(&cfg) 493 .execute() 494 .unwrap(); 495 assert_eq!(u, "c"); 496 assert_eq!(p, "b"); 497 } 498 499 #[test] credential_helper4()500 fn credential_helper4() { 501 if cfg!(windows) { 502 return; 503 } // shell scripts don't work on Windows 504 505 let td = TempDir::new().unwrap(); 506 let path = td.path().join("script"); 507 File::create(&path) 508 .unwrap() 509 .write( 510 br"\ 511 #!/bin/sh 512 echo username=c 513 ", 514 ) 515 .unwrap(); 516 chmod(&path); 517 let cfg = test_cfg! { 518 "credential.https://example.com.helper" => 519 &path.display().to_string()[..], 520 "credential.helper" => "!f() { echo username=a; echo password=b; }; f" 521 }; 522 let (u, p) = CredentialHelper::new("https://example.com/foo/bar") 523 .config(&cfg) 524 .execute() 525 .unwrap(); 526 assert_eq!(u, "c"); 527 assert_eq!(p, "b"); 528 } 529 530 #[test] credential_helper5()531 fn credential_helper5() { 532 if cfg!(windows) { 533 return; 534 } // shell scripts don't work on Windows 535 let td = TempDir::new().unwrap(); 536 let path = td.path().join("git-credential-script"); 537 File::create(&path) 538 .unwrap() 539 .write( 540 br"\ 541 #!/bin/sh 542 echo username=c 543 ", 544 ) 545 .unwrap(); 546 chmod(&path); 547 548 let paths = env::var("PATH").unwrap(); 549 let paths = 550 env::split_paths(&paths).chain(path.parent().map(|p| p.to_path_buf()).into_iter()); 551 env::set_var("PATH", &env::join_paths(paths).unwrap()); 552 553 let cfg = test_cfg! { 554 "credential.https://example.com.helper" => "script", 555 "credential.helper" => "!f() { echo username=a; echo password=b; }; f" 556 }; 557 let (u, p) = CredentialHelper::new("https://example.com/foo/bar") 558 .config(&cfg) 559 .execute() 560 .unwrap(); 561 assert_eq!(u, "c"); 562 assert_eq!(p, "b"); 563 } 564 565 #[test] credential_helper6()566 fn credential_helper6() { 567 let cfg = test_cfg! { 568 "credential.helper" => "" 569 }; 570 assert!(CredentialHelper::new("https://example.com/foo/bar") 571 .config(&cfg) 572 .execute() 573 .is_none()); 574 } 575 576 #[test] credential_helper7()577 fn credential_helper7() { 578 if cfg!(windows) { 579 return; 580 } // shell scripts don't work on Windows 581 let td = TempDir::new().unwrap(); 582 let path = td.path().join("script"); 583 File::create(&path) 584 .unwrap() 585 .write( 586 br"\ 587 #!/bin/sh 588 echo username=$1 589 echo password=$2 590 ", 591 ) 592 .unwrap(); 593 chmod(&path); 594 let cfg = test_cfg! { 595 "credential.helper" => &format!("{} a b", path.display()) 596 }; 597 let (u, p) = CredentialHelper::new("https://example.com/foo/bar") 598 .config(&cfg) 599 .execute() 600 .unwrap(); 601 assert_eq!(u, "a"); 602 assert_eq!(p, "b"); 603 } 604 605 #[test] 606 #[cfg(feature = "ssh")] ssh_key_from_memory()607 fn ssh_key_from_memory() { 608 let cred = Cred::ssh_key_from_memory( 609 "test", 610 Some("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDByAO8uj+kXicj6C2ODMspgmUoVyl5eaw8vR6a1yEnFuJFzevabNlN6Ut+CPT3TRnYk5BW73pyXBtnSL2X95BOnbjMDXc4YIkgs3YYHWnxbqsD4Pj/RoGqhf+gwhOBtL0poh8tT8WqXZYxdJQKLQC7oBqf3ykCEYulE4oeRUmNh4IzEE+skD/zDkaJ+S1HRD8D8YCiTO01qQnSmoDFdmIZTi8MS8Cw+O/Qhym1271ThMlhD6PubSYJXfE6rVbE7A9RzH73A6MmKBlzK8VTb4SlNSrr/DOk+L0uq+wPkv+pm+D9WtxoqQ9yl6FaK1cPawa3+7yRNle3m+72KCtyMkQv"), 611 r#" 612 -----BEGIN RSA PRIVATE KEY----- 613 Proc-Type: 4,ENCRYPTED 614 DEK-Info: AES-128-CBC,818C7722D3B01F2161C2ACF6A5BBAAE8 615 616 3Cht4QB3PcoQ0I55j1B3m2ZzIC/mrh+K5nQeA1Vy2GBTMyM7yqGHqTOv7qLhJscd 617 H+cB0Pm6yCr3lYuNrcKWOCUto+91P7ikyARruHVwyIxKdNx15uNulOzQJHQWNbA4 618 RQHlhjON4atVo2FyJ6n+ujK6QiBg2PR5Vbbw/AtV6zBCFW3PhzDn+qqmHjpBFqj2 619 vZUUe+MkDQcaF5J45XMHahhSdo/uKCDhfbylExp/+ACWkvxdPpsvcARM6X434ucD 620 aPY+4i0/JyLkdbm0GFN9/q3i53qf4kCBhojFl4AYJdGI0AzAgbdTXZ7EJHbAGZHS 621 os5K0oTwDVXMI0sSE2I/qHxaZZsDP1dOKq6di6SFPUp8liYimm7rNintRX88Gl2L 622 g1ko9abp/NlgD0YY/3mad+NNAISDL/YfXq2fklH3En3/7ZrOVZFKfZXwQwas5g+p 623 VQPKi3+ae74iOjLyuPDSc1ePmhUNYeP+9rLSc0wiaiHqls+2blPPDxAGMEo63kbz 624 YPVjdmuVX4VWnyEsfTxxJdFDYGSNh6rlrrO1RFrex7kJvpg5gTX4M/FT8TfCd7Hn 625 M6adXsLMqwu5tz8FuDmAtVdq8zdSrgZeAbpJ9D3EDOmZ70xz4XBL19ImxDp+Qqs2 626 kQX7kobRzeeP2URfRoGr7XZikQWyQ2UASfPcQULY8R58QoZWWsQ4w51GZHg7TDnw 627 1DRo/0OgkK7Gqf215nFmMpB4uyi58cq3WFwWQa1IqslkObpVgBQZcNZb/hKUYPGk 628 g4zehfIgAfCdnQHwZvQ6Fdzhcs3SZeO+zVyuiZN3Gsi9HU0/1vpAKiuuOzcG02vF 629 b6Y6hwsAA9yphF3atI+ARD4ZwXdDfzuGb3yJglMT3Fr/xuLwAvdchRo1spANKA0E 630 tT5okLrK0H4wnHvf2SniVVWRhmJis0lQo9LjGGwRIdsPpVnJSDvaISIVF+fHT90r 631 HvxN8zXI93x9jcPtwp7puQ1C7ehKJK10sZ71OLIZeuUgwt+5DRunqg6evPco9Go7 632 UOGwcVhLY200KT+1k7zWzCS0yVQp2HRm6cxsZXAp4ClBSwIx15eIoLIrjZdJRjCq 633 COp6pZx1fnvJ9ERIvl5hon+Ty+renMcFKz2HmchC7egpcqIxW9Dsv6zjhHle6pxb 634 37GaEKHF2KA3RN+dSV/K8n+C9Yent5tx5Y9a/pMcgRGtgu+G+nyFmkPKn5Zt39yX 635 qDpyM0LtbRVZPs+MgiqoGIwYc/ujoCq7GL38gezsBQoHaTt79yYBqCp6UR0LMuZ5 636 f/7CtWqffgySfJ/0wjGidDAumDv8CK45AURpL/Z+tbFG3M9ar/LZz/Y6EyBcLtGY 637 Wwb4zs8zXIA0qHrjNTnPqHDvezziArYfgPjxCIHMZzms9Yn8+N02p39uIytqg434 638 BAlCqZ7GYdDFfTpWIwX+segTK9ux0KdBqcQv+9Fwwjkq9KySnRKqNl7ZJcefFZJq 639 c6PA1iinZWBjuaO1HKx3PFulrl0bcpR9Kud1ZIyfnh5rwYN8UQkkcR/wZPla04TY 640 8l5dq/LI/3G5sZXwUHKOcuQWTj7Saq7Q6gkKoMfqt0wC5bpZ1m17GHPoMz6GtX9O 641 -----END RSA PRIVATE KEY----- 642 "#, 643 Some("test123")); 644 assert!(cred.is_ok()); 645 } 646 647 #[cfg(unix)] chmod(path: &Path)648 fn chmod(path: &Path) { 649 use std::fs; 650 use std::os::unix::prelude::*; 651 let mut perms = fs::metadata(path).unwrap().permissions(); 652 perms.set_mode(0o755); 653 fs::set_permissions(path, perms).unwrap(); 654 } 655 #[cfg(windows)] chmod(_path: &Path)656 fn chmod(_path: &Path) {} 657 } 658