[go: up one dir, main page]

git2/
cred.rs

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