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