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