mod reader;
mod writer;
pub mod errors {
use thiserror::Error;
pub type Result<T> = std::result::Result<T, OpenSSHKeyError>;
#[derive(Error, Debug)]
pub enum OpenSSHKeyError {
#[error("I/O error")]
IO {
#[from]
source: std::io::Error,
},
#[error("invalid UTF-8")]
InvalidUtf8 {
#[from]
source: std::str::Utf8Error,
},
#[error("invalid base64: {detail}")]
InvalidBase64 { detail: String },
#[error("invalid key format")]
InvalidFormat,
#[error("unsupported keytype: {keytype}")]
UnsupportedKeyType { keytype: String },
#[error("unsupported curve: {curve}")]
UnsupportedCurve { curve: String },
}
}
use crate::errors::*;
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use md5::Md5;
use sha2::{Digest, Sha256};
use crate::reader::Reader;
use crate::writer::Writer;
use std::fmt;
use std::io::{BufRead, BufReader, Read};
const SSH_RSA: &str = "ssh-rsa";
const SSH_DSA: &str = "ssh-dss";
const SSH_ED25519: &str = "ssh-ed25519";
const SSH_ED25519_SK: &str = "sk-ssh-ed25519@openssh.com";
const SSH_ECDSA_256: &str = "ecdsa-sha2-nistp256";
const SSH_ECDSA_384: &str = "ecdsa-sha2-nistp384";
const SSH_ECDSA_521: &str = "ecdsa-sha2-nistp521";
const SSH_ECDSA_SK: &str = "sk-ecdsa-sha2-nistp256@openssh.com";
const NISTP_256: &str = "nistp256";
const NISTP_384: &str = "nistp384";
const NISTP_521: &str = "nistp521";
#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]
pub enum Curve {
Nistp256,
Nistp384,
Nistp521,
}
impl Curve {
fn get(curve: &str) -> Result<Self> {
Ok(match curve {
NISTP_256 => Curve::Nistp256,
NISTP_384 => Curve::Nistp384,
NISTP_521 => Curve::Nistp521,
_ => {
return Err(OpenSSHKeyError::UnsupportedCurve {
curve: curve.to_string(),
})
}
})
}
fn curvetype(self) -> &'static str {
match self {
Curve::Nistp256 => NISTP_256,
Curve::Nistp384 => NISTP_384,
Curve::Nistp521 => NISTP_521,
}
}
}
impl fmt::Display for Curve {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.curvetype())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Data {
Rsa {
exponent: Vec<u8>,
modulus: Vec<u8>,
},
Dsa {
p: Vec<u8>,
q: Vec<u8>,
g: Vec<u8>,
pub_key: Vec<u8>,
},
Ed25519 {
key: Vec<u8>,
},
Ed25519Sk {
key: Vec<u8>,
application: Vec<u8>,
},
Ecdsa {
curve: Curve,
key: Vec<u8>,
},
EcdsaSk {
curve: Curve,
key: Vec<u8>,
application: Vec<u8>,
},
}
#[derive(Clone, Debug, Eq)]
pub struct PublicKey {
pub options: Option<String>,
pub data: Data,
pub comment: Option<String>,
}
impl fmt::Display for PublicKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.to_key_format())
}
}
impl core::cmp::PartialEq for PublicKey {
fn eq(&self, other: &PublicKey) -> bool {
self.data == other.data
}
}
impl std::str::FromStr for PublicKey {
type Err = OpenSSHKeyError;
fn from_str(s: &str) -> Result<Self> {
PublicKey::parse(s)
}
}
impl PublicKey {
pub fn parse(key: &str) -> Result<Self> {
let key = key.trim();
PublicKey::try_key_parse(key).or_else(|e| {
let mut key_start = 0;
let mut escape = false;
let mut quote = false;
let mut marker = key.starts_with('@');
for (i, c) in key.chars().enumerate() {
if c == '\\' {
escape = true;
continue;
}
if escape {
escape = false;
continue;
}
if c == '"' {
quote = !quote;
}
if !quote && (c == ' ' || c == '\t') {
if marker {
marker = false;
continue;
} else {
key_start = i + 1;
break;
}
}
}
let mut parsed = PublicKey::try_key_parse(&key[key_start..]).map_err(|_| e)?;
parsed.options = Some(key[..key_start - 1].into());
Ok(parsed)
})
}
fn try_key_parse(key: &str) -> Result<Self> {
let mut parts = key.split_whitespace();
let keytype = parts.next().ok_or(OpenSSHKeyError::InvalidFormat)?;
let data = parts.next().ok_or(OpenSSHKeyError::InvalidFormat)?;
let comment = parts.next().and_then(|c| {
if c.is_empty() {
None
} else {
Some(c.to_string())
}
});
let buf = BASE64
.decode(data)
.map_err(|e| OpenSSHKeyError::InvalidBase64 {
detail: format!("{}", e),
})?;
let mut reader = Reader::new(&buf);
let data_keytype = reader.read_string()?;
if keytype != data_keytype {
return Err(OpenSSHKeyError::InvalidFormat);
}
let data = match keytype {
SSH_RSA => {
let e = reader.read_mpint()?;
let n = reader.read_mpint()?;
Data::Rsa {
exponent: e.into(),
modulus: n.into(),
}
}
SSH_DSA => {
let p = reader.read_mpint()?;
let q = reader.read_mpint()?;
let g = reader.read_mpint()?;
let pub_key = reader.read_mpint()?;
Data::Dsa {
p: p.into(),
q: q.into(),
g: g.into(),
pub_key: pub_key.into(),
}
}
SSH_ED25519 => {
let key = reader.read_bytes()?;
Data::Ed25519 { key: key.into() }
}
SSH_ED25519_SK => {
let key = reader.read_bytes()?;
let application = reader.read_bytes()?;
Data::Ed25519Sk {
key: key.into(),
application: application.into(),
}
}
SSH_ECDSA_256 | SSH_ECDSA_384 | SSH_ECDSA_521 => {
let curve = reader.read_string()?;
let key = reader.read_bytes()?;
Data::Ecdsa {
curve: Curve::get(curve)?,
key: key.into(),
}
}
SSH_ECDSA_SK => {
let curve = reader.read_string()?;
let key = reader.read_bytes()?;
let application = reader.read_bytes()?;
Data::EcdsaSk {
curve: Curve::get(curve)?,
key: key.into(),
application: application.into(),
}
}
_ => {
return Err(OpenSSHKeyError::UnsupportedKeyType {
keytype: keytype.to_string(),
})
}
};
Ok(PublicKey {
options: None,
data,
comment,
})
}
pub fn read_keys<R>(r: R) -> Result<Vec<Self>>
where
R: Read,
{
let keybuf = BufReader::new(r);
let mut keys = vec![];
for key in keybuf.lines() {
let key = key?;
if !key.is_empty() && !(key.trim().starts_with('#')) {
keys.push(PublicKey::parse(&key)?);
}
}
Ok(keys)
}
pub fn from_rsa(e: Vec<u8>, n: Vec<u8>) -> Self {
PublicKey {
options: None,
data: Data::Rsa {
exponent: e,
modulus: n,
},
comment: None,
}
}
pub fn from_dsa(p: Vec<u8>, q: Vec<u8>, g: Vec<u8>, pkey: Vec<u8>) -> Self {
PublicKey {
options: None,
data: Data::Dsa {
p,
q,
g,
pub_key: pkey,
},
comment: None,
}
}
pub fn keytype(&self) -> &'static str {
match self.data {
Data::Rsa { .. } => SSH_RSA,
Data::Dsa { .. } => SSH_DSA,
Data::Ed25519 { .. } => SSH_ED25519,
Data::Ed25519Sk { .. } => SSH_ED25519_SK,
Data::Ecdsa { ref curve, .. } => match *curve {
Curve::Nistp256 => SSH_ECDSA_256,
Curve::Nistp384 => SSH_ECDSA_384,
Curve::Nistp521 => SSH_ECDSA_521,
},
Data::EcdsaSk { .. } => SSH_ECDSA_SK,
}
}
pub fn data(&self) -> Vec<u8> {
let mut writer = Writer::new();
writer.write_string(self.keytype());
match self.data {
Data::Rsa {
ref exponent,
ref modulus,
} => {
writer.write_mpint(exponent.clone());
writer.write_mpint(modulus.clone());
}
Data::Dsa {
ref p,
ref q,
ref g,
ref pub_key,
} => {
writer.write_mpint(p.clone());
writer.write_mpint(q.clone());
writer.write_mpint(g.clone());
writer.write_mpint(pub_key.clone());
}
Data::Ed25519 { ref key } => {
writer.write_bytes(key.clone());
}
Data::Ed25519Sk {
ref key,
ref application,
} => {
writer.write_bytes(key.clone());
writer.write_bytes(application.clone());
}
Data::Ecdsa { ref curve, ref key } => {
writer.write_string(curve.curvetype());
writer.write_bytes(key.clone());
}
Data::EcdsaSk {
ref curve,
ref key,
ref application,
} => {
writer.write_string(curve.curvetype());
writer.write_bytes(key.clone());
writer.write_bytes(application.clone());
}
}
writer.into_vec()
}
pub fn set_comment(&mut self, comment: &str) {
self.comment = Some(comment.to_string());
}
pub fn to_key_format(&self) -> String {
let key = format!(
"{} {} {}",
self.keytype(),
BASE64.encode(self.data()),
self.comment.clone().unwrap_or_default()
);
if let Some(ref options) = self.options {
format!("{} {}", options, key)
} else {
key
}
}
pub fn size(&self) -> usize {
match self.data {
Data::Rsa { ref modulus, .. } => modulus.len() * 8,
Data::Dsa { ref p, .. } => p.len() * 8,
Data::Ed25519 { .. } | Data::Ed25519Sk { .. } => 256, Data::Ecdsa { ref curve, .. } | Data::EcdsaSk { ref curve, .. } => match *curve {
Curve::Nistp256 => 256,
Curve::Nistp384 => 384,
Curve::Nistp521 => 521,
},
}
}
pub fn fingerprint(&self) -> String {
let data = self.data();
let mut hasher = Sha256::new();
hasher.update(&data);
let hashed = hasher.finalize();
let mut fingerprint = BASE64.encode(hashed);
if let Some(l) = fingerprint.find('=') {
fingerprint.truncate(l);
};
fingerprint
}
pub fn to_fingerprint_string(&self) -> String {
let keytype = match self.data {
Data::Rsa { .. } => "RSA",
Data::Dsa { .. } => "DSA",
Data::Ed25519 { .. } => "ED25519",
Data::Ed25519Sk { .. } => "ED25519_SK",
Data::Ecdsa { .. } => "ECDSA",
Data::EcdsaSk { .. } => "ECDSA_SK",
};
let comment = self
.comment
.clone()
.unwrap_or_else(|| "no comment".to_string());
format!(
"{} SHA256:{} {} ({})",
self.size(),
self.fingerprint(),
comment,
keytype
)
}
pub fn fingerprint_md5(&self) -> String {
let mut sh = Md5::default();
sh.update(&self.data());
let md5: Vec<String> = sh.finalize().iter().map(|n| format!("{:02x}", n)).collect();
md5.join(":")
}
pub fn to_fingerprint_md5_string(&self) -> String {
let keytype = match self.data {
Data::Rsa { .. } => "RSA",
Data::Dsa { .. } => "DSA",
Data::Ed25519 { .. } => "ED25519",
Data::Ed25519Sk { .. } => "ED25519_SK",
Data::Ecdsa { .. } => "ECDSA",
Data::EcdsaSk { .. } => "ECDSA_SK",
};
let comment = self
.comment
.clone()
.unwrap_or_else(|| "no comment".to_string());
format!(
"{} MD5:{} {} ({})",
self.size(),
self.fingerprint_md5(),
comment,
keytype
)
}
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_RSA_KEY: &str = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCYH3vPUJThzriVlVKmKOg71EOVYm274oRa5KLWEoK0HmjMc9ru0j4ofouoeW/AVmRVujxfaIGR/8en/lUPkiv5DSeM6aXnDz5cExNptrAy/sMPLQhVALRrqQ+dkS9Ct/YA+A1Le5LPh4MJu79hCDLTwqSdKqDuUcYQzR0M7APslaDCR96zY+VUL4lKObUUd4wsP3opdTQ6G20qXEer14EPGr9N53S/u+JJGLoPlb1uPIH96oKY4t/SeLIRQsocdViRaiF/Aq7kPzWd/yCLVdXJSRt3CftboV4kLBHGteTS551J32MJoqjEi4Q/DucWYrQfx5H3qXVB+/G2HurKPIHL demos@siril";
const TEST_RSA_COMMENT_KEY: &str = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCYH3vPUJThzriVlVKmKOg71EOVYm274oRa5KLWEoK0HmjMc9ru0j4ofouoeW/AVmRVujxfaIGR/8en/lUPkiv5DSeM6aXnDz5cExNptrAy/sMPLQhVALRrqQ+dkS9Ct/YA+A1Le5LPh4MJu79hCDLTwqSdKqDuUcYQzR0M7APslaDCR96zY+VUL4lKObUUd4wsP3opdTQ6G20qXEer14EPGr9N53S/u+JJGLoPlb1uPIH96oKY4t/SeLIRQsocdViRaiF/Aq7kPzWd/yCLVdXJSRt3CftboV4kLBHGteTS551J32MJoqjEi4Q/DucWYrQfx5H3qXVB+/G2HurKPIHL test";
const TEST_DSA_KEY: &str = "ssh-dss AAAAB3NzaC1kc3MAAACBAIkd9CkqldM2St8f53rfJT7kPgiA8leZaN7hdZd48hYJyKzVLoPdBMaGFuOwGjv0Im3JWqWAewANe0xeLceQL0rSFbM/mZV+1gc1nm1WmtVw4KJIlLXl3gS7NYfQ9Ith4wFnZd/xhRz9Q+MBsA1DgXew1zz4dLYI46KmFivJ7XDzAAAAFQC8z4VIhI4HlHTvB7FdwAfqWsvcOwAAAIBEqPIkW3HHDTSEhUhhV2AlIPNwI/bqaCXy2zYQ6iTT3oUh+N4xlRaBSvW+h2NC97U8cxd7Y0dXIbQKPzwNzRX1KA1F9WAuNzrx9KkpCg2TpqXShhp+Sseb+l6uJjthIYM6/0dvr9cBDMeExabPPgBo3Eii2NLbFSqIe86qav8hZAAAAIBk5AetZrG8varnzv1khkKh6Xq/nX9r1UgIOCQos2XOi2ErjlB9swYCzReo1RT7dalITVi7K9BtvJxbutQEOvN7JjJnPJs+M3OqRMMF+anXPdCWUIBxZUwctbkAD5joEjGDrNXHQEw9XixZ9p3wudbISnPFgZhS1sbS9Rlw5QogKg== demos@siril";
const TEST_ED25519_KEY: &str = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAhBr6++FQXB8kkgOMbdxBuyrHzuX5HkElswrN6DQoN/ demos@siril";
const TEST_ED25519_SK_KEY: &str = "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIEX/dQ0v4127bEo8eeG1EV0ApO2lWbSnN6RWusn/NjqIAAAABHNzaDo= demos@siril";
const TEST_ECDSA256_KEY: &str = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIhfLQrww4DlhYzbSWXoX3ctOQ0jVosvfHfW+QWVotksbPzM2YgkIikTpoHUfZrYpJKWx7WYs5aqeLkdCDdk+jk= demos@siril";
const TEST_ECDSA_SK_KEY: &str = "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBDZ+f5tSRhlB7EN39f93SscTN5PUvbD3UQsNrlE1ZdbwPMMRul2zlPiUvwAvnJitW0jlD/vwZOW2YN+q+iZ5c0MAAAAEc3NoOg== demos@siril";
#[test]
fn rsa_parse_to_string() {
let key = PublicKey::parse(TEST_RSA_KEY).unwrap();
let out = key.to_string();
assert_eq!(TEST_RSA_KEY, out);
}
#[test]
fn rsa_size() {
let key = PublicKey::parse(TEST_RSA_KEY).unwrap();
assert_eq!(2048, key.size());
}
#[test]
fn rsa_keytype() {
let key = PublicKey::parse(TEST_RSA_KEY).unwrap();
assert_eq!("ssh-rsa", key.keytype());
}
#[test]
fn rsa_fingerprint() {
let key = PublicKey::parse(TEST_RSA_KEY).unwrap();
assert_eq!(
"YTw/JyJmeAAle1/7zuZkPP0C73BQ+6XrFEt2/Wy++2o",
key.fingerprint()
);
}
#[test]
fn rsa_fingerprint_string() {
let key = PublicKey::parse(TEST_RSA_KEY).unwrap();
assert_eq!(
"2048 SHA256:YTw/JyJmeAAle1/7zuZkPP0C73BQ+6XrFEt2/Wy++2o demos@siril (RSA)",
key.to_fingerprint_string()
);
}
#[test]
fn rsa_fingerprint_md5() {
let key = PublicKey::parse(TEST_RSA_KEY).unwrap();
assert_eq!(
"e9:a1:5b:cd:a3:69:d2:d9:17:cb:09:3e:78:e1:0d:dd",
key.fingerprint_md5()
);
}
#[test]
fn rsa_fingerprint_md5_string() {
let key = PublicKey::parse(TEST_RSA_KEY).unwrap();
assert_eq!(
"2048 MD5:e9:a1:5b:cd:a3:69:d2:d9:17:cb:09:3e:78:e1:0d:dd demos@siril (RSA)",
key.to_fingerprint_md5_string()
);
}
#[test]
fn rsa_set_comment() {
let mut key = PublicKey::parse(TEST_RSA_KEY).unwrap();
key.set_comment("test");
let out = key.to_string();
assert_eq!(TEST_RSA_COMMENT_KEY, out);
}
#[test]
fn dsa_parse_to_string() {
let key = PublicKey::parse(TEST_DSA_KEY).unwrap();
let out = key.to_string();
assert_eq!(TEST_DSA_KEY, out);
}
#[test]
fn dsa_size() {
let key = PublicKey::parse(TEST_DSA_KEY).unwrap();
assert_eq!(1024, key.size());
}
#[test]
fn dsa_keytype() {
let key = PublicKey::parse(TEST_DSA_KEY).unwrap();
assert_eq!("ssh-dss", key.keytype());
}
#[test]
fn dsa_fingerprint() {
let key = PublicKey::parse(TEST_DSA_KEY).unwrap();
assert_eq!(
"/Pyxrjot1Hs5PN2Dpg/4pK2wxxtP9Igc3sDTAWIEXT4",
key.fingerprint()
);
}
#[test]
fn dsa_fingerprint_string() {
let key = PublicKey::parse(TEST_DSA_KEY).unwrap();
assert_eq!(
"1024 SHA256:/Pyxrjot1Hs5PN2Dpg/4pK2wxxtP9Igc3sDTAWIEXT4 demos@siril (DSA)",
key.to_fingerprint_string()
);
}
#[test]
fn ed25519_parse_to_string() {
let key = PublicKey::parse(TEST_ED25519_KEY).unwrap();
let out = key.to_string();
assert_eq!(TEST_ED25519_KEY, out);
}
#[test]
fn ed25519_size() {
let key = PublicKey::parse(TEST_ED25519_KEY).unwrap();
assert_eq!(256, key.size());
}
#[test]
fn ed25519_keytype() {
let key = PublicKey::parse(TEST_ED25519_KEY).unwrap();
assert_eq!("ssh-ed25519", key.keytype());
}
#[test]
fn ed25519_fingerprint() {
let key = PublicKey::parse(TEST_ED25519_KEY).unwrap();
assert_eq!(
"A/lHzXxsgbp11dcKKfSDyNQIdep7EQgZEoRYVDBfNdI",
key.fingerprint()
);
}
#[test]
fn ed25519_fingerprint_string() {
let key = PublicKey::parse(TEST_ED25519_KEY).unwrap();
assert_eq!(
"256 SHA256:A/lHzXxsgbp11dcKKfSDyNQIdep7EQgZEoRYVDBfNdI demos@siril (ED25519)",
key.to_fingerprint_string()
);
}
#[test]
fn ed25519_sk_parse_to_string() {
let key = PublicKey::parse(TEST_ED25519_SK_KEY).unwrap();
let out = key.to_string();
assert_eq!(TEST_ED25519_SK_KEY, out);
}
#[test]
fn ed25519_sk_size() {
let key = PublicKey::parse(TEST_ED25519_SK_KEY).unwrap();
assert_eq!(256, key.size());
}
#[test]
fn ed25519_sk_keytype() {
let key = PublicKey::parse(TEST_ED25519_SK_KEY).unwrap();
assert_eq!("sk-ssh-ed25519@openssh.com", key.keytype());
}
#[test]
fn ed25519_sk_fingerprint() {
let key = PublicKey::parse(TEST_ED25519_SK_KEY).unwrap();
assert_eq!(
"U8IKRkIHed6vFMTflwweA3HhIf2DWgZ8EFTm9fgwOUk",
key.fingerprint()
);
}
#[test]
fn ed25519_sk_fingerprint_string() {
let key = PublicKey::parse(TEST_ED25519_SK_KEY).unwrap();
assert_eq!(
"256 SHA256:U8IKRkIHed6vFMTflwweA3HhIf2DWgZ8EFTm9fgwOUk demos@siril (ED25519_SK)",
key.to_fingerprint_string()
);
}
#[test]
fn ecdsa256_parse_to_string() {
let key = PublicKey::parse(TEST_ECDSA256_KEY).unwrap();
let out = key.to_string();
assert_eq!(TEST_ECDSA256_KEY, out);
}
#[test]
fn ecdsa256_size() {
let key = PublicKey::parse(TEST_ECDSA256_KEY).unwrap();
assert_eq!(256, key.size());
}
#[test]
fn ecdsa256_keytype() {
let key = PublicKey::parse(TEST_ECDSA256_KEY).unwrap();
assert_eq!("ecdsa-sha2-nistp256", key.keytype());
}
#[test]
fn ecdsa256_fingerprint() {
let key = PublicKey::parse(TEST_ECDSA256_KEY).unwrap();
assert_eq!(
"BzS5YXMW/d2vFk8Oqh+nKmvKr8X/FTLBfJgDGLu5GAs",
key.fingerprint()
);
}
#[test]
fn ecdsa256_fingerprint_string() {
let key = PublicKey::parse(TEST_ECDSA256_KEY).unwrap();
assert_eq!(
"256 SHA256:BzS5YXMW/d2vFk8Oqh+nKmvKr8X/FTLBfJgDGLu5GAs demos@siril (ECDSA)",
key.to_fingerprint_string()
);
}
#[test]
fn ecdsa_sk_parse_to_string() {
let key = PublicKey::parse(TEST_ECDSA_SK_KEY).unwrap();
let out = key.to_string();
assert_eq!(TEST_ECDSA_SK_KEY, out);
}
#[test]
fn ecdsa_sk_size() {
let key = PublicKey::parse(TEST_ECDSA_SK_KEY).unwrap();
assert_eq!(256, key.size());
}
#[test]
fn ecdsa_sk_keytype() {
let key = PublicKey::parse(TEST_ECDSA_SK_KEY).unwrap();
assert_eq!("sk-ecdsa-sha2-nistp256@openssh.com", key.keytype());
}
#[test]
fn ecdsa_sk_fingerprint() {
let key = PublicKey::parse(TEST_ECDSA_SK_KEY).unwrap();
assert_eq!(
"N0sNKBgWKK8usPuPegtgzHQQA9vQ/dRhAEhwFDAnLA4",
key.fingerprint()
);
}
#[test]
fn ecdsa_sk_fingerprint_string() {
let key = PublicKey::parse(TEST_ECDSA_SK_KEY).unwrap();
assert_eq!(
"256 SHA256:N0sNKBgWKK8usPuPegtgzHQQA9vQ/dRhAEhwFDAnLA4 demos@siril (ECDSA_SK)",
key.to_fingerprint_string()
);
}
#[test]
fn option_parse() {
let key = PublicKey::parse("agent-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAhBr6++FQXB8kkgOMbdxBuyrHzuX5HkElswrN6DQoN/ demos@siril").unwrap();
assert_eq!(Some("agent-forwarding".into()), key.options);
assert_eq!("agent-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAhBr6++FQXB8kkgOMbdxBuyrHzuX5HkElswrN6DQoN/ demos@siril", key.to_string());
let key = PublicKey::parse("from=\"*.sales.example.net,!pc.sales.example.net\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAhBr6++FQXB8kkgOMbdxBuyrHzuX5HkElswrN6DQoN/ demos@siril").unwrap();
assert_eq!(
Some("from=\"*.sales.example.net,!pc.sales.example.net\"".into()),
key.options
);
assert_eq!("from=\"*.sales.example.net,!pc.sales.example.net\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAhBr6++FQXB8kkgOMbdxBuyrHzuX5HkElswrN6DQoN/ demos@siril", key.to_string());
let key = PublicKey::parse("permitopen=\"192.0.2.1:80\",permitopen=\"192.0.2.2:25\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAhBr6++FQXB8kkgOMbdxBuyrHzuX5HkElswrN6DQoN/ demos@siril").unwrap();
assert_eq!(
Some("permitopen=\"192.0.2.1:80\",permitopen=\"192.0.2.2:25\"".into()),
key.options
);
assert_eq!("permitopen=\"192.0.2.1:80\",permitopen=\"192.0.2.2:25\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAhBr6++FQXB8kkgOMbdxBuyrHzuX5HkElswrN6DQoN/ demos@siril", key.to_string());
let key = PublicKey::parse("command=\"echo \\\"holy shell escaping batman\\\"\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAhBr6++FQXB8kkgOMbdxBuyrHzuX5HkElswrN6DQoN/ demos@siril").unwrap();
assert_eq!(
Some("command=\"echo \\\"holy shell escaping batman\\\"\"".into()),
key.options
);
assert_eq!("command=\"echo \\\"holy shell escaping batman\\\"\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAhBr6++FQXB8kkgOMbdxBuyrHzuX5HkElswrN6DQoN/ demos@siril", key.to_string());
let key = PublicKey::parse("command=\"dump /home\",no-pty,no-port-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAhBr6++FQXB8kkgOMbdxBuyrHzuX5HkElswrN6DQoN/ demos@siril").unwrap();
assert_eq!(
Some("command=\"dump /home\",no-pty,no-port-forwarding".into()),
key.options
);
assert_eq!("command=\"dump /home\",no-pty,no-port-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAhBr6++FQXB8kkgOMbdxBuyrHzuX5HkElswrN6DQoN/ demos@siril", key.to_string());
}
#[test]
fn hostname_parse() {
let key = PublicKey::parse("ec2-52-53-211-129.us-west-1.compute.amazonaws.com,52.53.211.129 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFHnC16I49ccjBo68lvN1+zpnAuTGbZjHFi2JRgPZK5o02UDCrFYCUhuS3oCh75+6YmVyReLZAyAM7S/5wjMzTY=").unwrap();
assert_eq!(
Some("ec2-52-53-211-129.us-west-1.compute.amazonaws.com,52.53.211.129".into()),
key.options
);
assert_eq!("ec2-52-53-211-129.us-west-1.compute.amazonaws.com,52.53.211.129 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFHnC16I49ccjBo68lvN1+zpnAuTGbZjHFi2JRgPZK5o02UDCrFYCUhuS3oCh75+6YmVyReLZAyAM7S/5wjMzTY=", key.to_string().trim());
let key = PublicKey::parse("[fangorn.csh.rit.edu]:9090,[129.21.50.131]:9090 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAopjUBQqif5ILeoMHjJ9wGlGs2eNHEv3+OAiiEDHCapNm3guNa+T/ZMtedaC/0P8bLBCXMiyNQU04N/IRyN3Mp/SGhtGJl1PDENXzPB9aoxsB2HHc8s8P7mxal1G4BtCT/fJM5XywEHWAcHkzW91iTK+ApAdqt6AHj35ogil9maFlUNKcXz2aW27hdbDtC0fautvWd9RIITHPq00rdvaHjRcc2msv8LddhBkStP8FrB39RPu9M+ikBkTwdQTSGcIBDYJgt3la2KMwmU1F81cq17wb21lPriBwr626lBiir/WdrBsoAsANeZfyzpAm8K4ssI3eu9eklxpEKdAdNRJbpQ==").unwrap();
assert_eq!(
Some("[fangorn.csh.rit.edu]:9090,[129.21.50.131]:9090".into()),
key.options
);
assert_eq!("[fangorn.csh.rit.edu]:9090,[129.21.50.131]:9090 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAopjUBQqif5ILeoMHjJ9wGlGs2eNHEv3+OAiiEDHCapNm3guNa+T/ZMtedaC/0P8bLBCXMiyNQU04N/IRyN3Mp/SGhtGJl1PDENXzPB9aoxsB2HHc8s8P7mxal1G4BtCT/fJM5XywEHWAcHkzW91iTK+ApAdqt6AHj35ogil9maFlUNKcXz2aW27hdbDtC0fautvWd9RIITHPq00rdvaHjRcc2msv8LddhBkStP8FrB39RPu9M+ikBkTwdQTSGcIBDYJgt3la2KMwmU1F81cq17wb21lPriBwr626lBiir/WdrBsoAsANeZfyzpAm8K4ssI3eu9eklxpEKdAdNRJbpQ==", key.to_string().trim());
let key = PublicKey::parse("@revoked [fangorn.csh.rit.edu]:9090,[129.21.50.131]:9090 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAopjUBQqif5ILeoMHjJ9wGlGs2eNHEv3+OAiiEDHCapNm3guNa+T/ZMtedaC/0P8bLBCXMiyNQU04N/IRyN3Mp/SGhtGJl1PDENXzPB9aoxsB2HHc8s8P7mxal1G4BtCT/fJM5XywEHWAcHkzW91iTK+ApAdqt6AHj35ogil9maFlUNKcXz2aW27hdbDtC0fautvWd9RIITHPq00rdvaHjRcc2msv8LddhBkStP8FrB39RPu9M+ikBkTwdQTSGcIBDYJgt3la2KMwmU1F81cq17wb21lPriBwr626lBiir/WdrBsoAsANeZfyzpAm8K4ssI3eu9eklxpEKdAdNRJbpQ==").unwrap();
assert_eq!(
Some("@revoked [fangorn.csh.rit.edu]:9090,[129.21.50.131]:9090".into()),
key.options
);
assert_eq!("@revoked [fangorn.csh.rit.edu]:9090,[129.21.50.131]:9090 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAopjUBQqif5ILeoMHjJ9wGlGs2eNHEv3+OAiiEDHCapNm3guNa+T/ZMtedaC/0P8bLBCXMiyNQU04N/IRyN3Mp/SGhtGJl1PDENXzPB9aoxsB2HHc8s8P7mxal1G4BtCT/fJM5XywEHWAcHkzW91iTK+ApAdqt6AHj35ogil9maFlUNKcXz2aW27hdbDtC0fautvWd9RIITHPq00rdvaHjRcc2msv8LddhBkStP8FrB39RPu9M+ikBkTwdQTSGcIBDYJgt3la2KMwmU1F81cq17wb21lPriBwr626lBiir/WdrBsoAsANeZfyzpAm8K4ssI3eu9eklxpEKdAdNRJbpQ==", key.to_string().trim());
let key = PublicKey::parse("@cert-authority [fangorn.csh.rit.edu]:9090,[129.21.50.131]:9090 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAopjUBQqif5ILeoMHjJ9wGlGs2eNHEv3+OAiiEDHCapNm3guNa+T/ZMtedaC/0P8bLBCXMiyNQU04N/IRyN3Mp/SGhtGJl1PDENXzPB9aoxsB2HHc8s8P7mxal1G4BtCT/fJM5XywEHWAcHkzW91iTK+ApAdqt6AHj35ogil9maFlUNKcXz2aW27hdbDtC0fautvWd9RIITHPq00rdvaHjRcc2msv8LddhBkStP8FrB39RPu9M+ikBkTwdQTSGcIBDYJgt3la2KMwmU1F81cq17wb21lPriBwr626lBiir/WdrBsoAsANeZfyzpAm8K4ssI3eu9eklxpEKdAdNRJbpQ==").unwrap();
assert_eq!(
Some("@cert-authority [fangorn.csh.rit.edu]:9090,[129.21.50.131]:9090".into()),
key.options
);
assert_eq!("@cert-authority [fangorn.csh.rit.edu]:9090,[129.21.50.131]:9090 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAopjUBQqif5ILeoMHjJ9wGlGs2eNHEv3+OAiiEDHCapNm3guNa+T/ZMtedaC/0P8bLBCXMiyNQU04N/IRyN3Mp/SGhtGJl1PDENXzPB9aoxsB2HHc8s8P7mxal1G4BtCT/fJM5XywEHWAcHkzW91iTK+ApAdqt6AHj35ogil9maFlUNKcXz2aW27hdbDtC0fautvWd9RIITHPq00rdvaHjRcc2msv8LddhBkStP8FrB39RPu9M+ikBkTwdQTSGcIBDYJgt3la2KMwmU1F81cq17wb21lPriBwr626lBiir/WdrBsoAsANeZfyzpAm8K4ssI3eu9eklxpEKdAdNRJbpQ==", key.to_string().trim());
}
#[test]
fn read_keys() {
let authorized_keys = "# authorized keys
command=\"echo \\\"holy shell escaping batman\\\"\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAhBr6++FQXB8kkgOMbdxBuyrHzuX5HkElswrN6DQoN/ demos@siril
agent-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAhBr6++FQXB8kkgOMbdxBuyrHzuX5HkElswrN6DQoN/ demos@siril
ssh-dss AAAAB3NzaC1kc3MAAACBAIkd9CkqldM2St8f53rfJT7kPgiA8leZaN7hdZd48hYJyKzVLoPdBMaGFuOwGjv0Im3JWqWAewANe0xeLceQL0rSFbM/mZV+1gc1nm1WmtVw4KJIlLXl3gS7NYfQ9Ith4wFnZd/xhRz9Q+MBsA1DgXew1zz4dLYI46KmFivJ7XDzAAAAFQC8z4VIhI4HlHTvB7FdwAfqWsvcOwAAAIBEqPIkW3HHDTSEhUhhV2AlIPNwI/bqaCXy2zYQ6iTT3oUh+N4xlRaBSvW+h2NC97U8cxd7Y0dXIbQKPzwNzRX1KA1F9WAuNzrx9KkpCg2TpqXShhp+Sseb+l6uJjthIYM6/0dvr9cBDMeExabPPgBo3Eii2NLbFSqIe86qav8hZAAAAIBk5AetZrG8varnzv1khkKh6Xq/nX9r1UgIOCQos2XOi2ErjlB9swYCzReo1RT7dalITVi7K9BtvJxbutQEOvN7JjJnPJs+M3OqRMMF+anXPdCWUIBxZUwctbkAD5joEjGDrNXHQEw9XixZ9p3wudbISnPFgZhS1sbS9Rlw5QogKg==
";
let key1 = "command=\"echo \\\"holy shell escaping batman\\\"\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAhBr6++FQXB8kkgOMbdxBuyrHzuX5HkElswrN6DQoN/ demos@siril";
let key2 = "agent-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAhBr6++FQXB8kkgOMbdxBuyrHzuX5HkElswrN6DQoN/ demos@siril";
let key3 = "ssh-dss AAAAB3NzaC1kc3MAAACBAIkd9CkqldM2St8f53rfJT7kPgiA8leZaN7hdZd48hYJyKzVLoPdBMaGFuOwGjv0Im3JWqWAewANe0xeLceQL0rSFbM/mZV+1gc1nm1WmtVw4KJIlLXl3gS7NYfQ9Ith4wFnZd/xhRz9Q+MBsA1DgXew1zz4dLYI46KmFivJ7XDzAAAAFQC8z4VIhI4HlHTvB7FdwAfqWsvcOwAAAIBEqPIkW3HHDTSEhUhhV2AlIPNwI/bqaCXy2zYQ6iTT3oUh+N4xlRaBSvW+h2NC97U8cxd7Y0dXIbQKPzwNzRX1KA1F9WAuNzrx9KkpCg2TpqXShhp+Sseb+l6uJjthIYM6/0dvr9cBDMeExabPPgBo3Eii2NLbFSqIe86qav8hZAAAAIBk5AetZrG8varnzv1khkKh6Xq/nX9r1UgIOCQos2XOi2ErjlB9swYCzReo1RT7dalITVi7K9BtvJxbutQEOvN7JjJnPJs+M3OqRMMF+anXPdCWUIBxZUwctbkAD5joEjGDrNXHQEw9XixZ9p3wudbISnPFgZhS1sbS9Rlw5QogKg== ";
let keys = PublicKey::read_keys(authorized_keys.as_bytes()).unwrap();
assert_eq!(key1, keys[0].to_string());
assert_eq!(key2, keys[1].to_string());
assert_eq!(key3, keys[2].to_string());
}
}