1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
use crate::error::{Error, Result};
use regex::Regex;
use ssh2::KnownHostKeyFormat;
use std::convert::TryFrom;

#[derive(Clone, Debug)]
pub struct KnownHost {
  pub hostname: String,
  pub key_format: KnownHostKeyFormat,
  pub fingerprint: Vec<u8>,
  pub comment: Option<String>,
}

impl TryFrom<&str> for KnownHost {
  type Error = crate::error::Error;
  fn try_from(value: &str) -> Result<Self> {
    let re = Regex::new(r"(?P<host>\S*) (?P<type>\S*) (?P<hash>\S*) ?(?P<comment>.*)?")?;
    let captures = re
      .captures(value)
      .ok_or_else(|| Error::KnownHostParsingError("invalid known host format.".to_string()))?;

    let hostname = captures
      .name("host")
      .ok_or_else(|| Error::KnownHostParsingError("cannot get hostname.".to_string()))?
      .as_str()
      .to_string();
    let key_format = captures
      .name("type")
      .ok_or_else(|| Error::KnownHostParsingError("cannot get key format.".to_string()))?
      .as_str();
    let fingerprint = captures
      .name("hash")
      .ok_or_else(|| Error::KnownHostParsingError("cannot get fingerprint.".to_string()))?
      .as_str();
    let fingerprint = base64::decode(fingerprint)?;
    let comment = captures
      .name("comment")
      .map(|c| c.as_str().to_string())
      .filter(|c| !c.is_empty());

    let key_format = match key_format {
      "rsa1" => KnownHostKeyFormat::Rsa1,
      "ssh-rsa" => KnownHostKeyFormat::SshRsa,
      "ssh-dss" => KnownHostKeyFormat::SshDss,
      "ecdsa-sha2-nistp256" => KnownHostKeyFormat::Ecdsa256,
      "ecdsa-sha2-nistp384" => KnownHostKeyFormat::Ecdsa384,
      "ecdsa-sha2-nistp521" => KnownHostKeyFormat::Ecdsa521,
      "ssh-ed25519" => KnownHostKeyFormat::Ed255219,
      _ => KnownHostKeyFormat::Unknown,
    };

    Ok(KnownHost {
      hostname,
      key_format,
      fingerprint,
      comment,
    })
  }
}

#[test]
pub fn test_known_host() {
  let known_host = KnownHost::try_from("localhost ssh-ed25519 SGVsbG8gV29ybGQh").unwrap();
  assert_eq!("localhost", &known_host.hostname);
  assert_eq!(
    format!("{:?}", KnownHostKeyFormat::Ed255219),
    format!("{:?}", known_host.key_format)
  );
  assert_eq!("Hello World!".as_bytes().to_vec(), known_host.fingerprint);
  assert_eq!(None, known_host.comment);
}