use std::collections::HashMap;
use std::fmt;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use ring::{
digest,
rand::{SecureRandom, SystemRandom},
signature::{
RsaPublicKeyComponents, UnparsedPublicKey, ECDSA_P256_SHA256_FIXED,
ECDSA_P384_SHA384_FIXED, ED25519, RSA_PKCS1_2048_8192_SHA1_FOR_LEGACY_USE_ONLY,
RSA_PKCS1_2048_8192_SHA256, RSA_PKCS1_2048_8192_SHA512,
},
};
use super::SSHCertificateSigner;
use super::{
keytype::KeyType,
pubkey::{PublicKey, PublicKeyKind},
reader::Reader,
writer::Writer,
};
use crate::{error::Error, Result};
use std::convert::TryFrom;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum CertType {
User = 1,
Host = 2,
}
impl TryFrom<&str> for CertType {
type Error = &'static str;
fn try_from(s: &str) -> std::result::Result<Self, Self::Error> {
match s {
"user" | "User" => Ok(CertType::User),
"host" | "Host" => Ok(CertType::Host),
_ => Err("Unknown certificate type"),
}
}
}
impl fmt::Display for CertType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
CertType::User => write!(f, "user certificate"),
CertType::Host => write!(f, "host certificate"),
}
}
}
const STANDARD_EXTENSIONS: [(&str, &str); 5] = [
("permit-agent-forwarding", ""),
("permit-port-forwarding", ""),
("permit-pty", ""),
("permit-user-rc", ""),
("permit-X11-forwarding", ""),
];
#[derive(Debug, PartialEq, Eq)]
pub struct Certificate {
pub key_type: KeyType,
pub nonce: Vec<u8>,
pub key: PublicKey,
pub serial: u64,
pub cert_type: CertType,
pub key_id: String,
pub principals: Vec<String>,
pub valid_after: u64,
pub valid_before: u64,
pub critical_options: HashMap<String, String>,
pub extensions: HashMap<String, String>,
pub reserved: Vec<u8>,
pub signature_key: PublicKey,
pub signature: Vec<u8>,
pub comment: Option<String>,
pub serialized: Vec<u8>,
}
impl Certificate {
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Certificate> {
let mut contents = String::new();
File::open(path)?.read_to_string(&mut contents)?;
Certificate::from_string(&contents)
}
pub fn from_string(s: &str) -> Result<Certificate> {
let mut iter = s.split_whitespace();
let outer_kt = KeyType::from_name(iter.next().ok_or(Error::InvalidFormat)?)?;
let data = iter.next().ok_or(Error::InvalidFormat)?;
let comment = iter.next().map(String::from);
let decoded = base64::decode(data)?;
let mut cert = Certificate::from_bytes(&decoded)?;
cert.comment = comment;
if cert.key_type.kind != outer_kt.kind {
return Err(Error::KeyTypeMismatch);
}
Ok(cert)
}
pub fn from_bytes(data: &[u8]) -> Result<Certificate> {
let mut reader = Reader::new(&data);
let kt_name = reader.read_string()?;
let key_type = KeyType::from_name(&kt_name)?;
if !key_type.is_cert {
return Err(Error::NotCertificate);
}
let nonce = reader.read_bytes()?;
let key = PublicKey::from_reader(&key_type.as_pubkey_name(), &mut reader)?;
let serial = reader.read_u64()?;
let cert_type = match reader.read_u32()? {
1 => CertType::User,
2 => CertType::Host,
n => return Err(Error::InvalidCertType(n)),
};
let key_id = reader.read_string()?;
let principals = reader.read_bytes().and_then(|v| read_principals(&v))?;
let valid_after = reader.read_u64()?;
let valid_before = reader.read_u64()?;
let critical_options = reader.read_bytes().and_then(|v| read_options(&v))?;
let extensions = reader.read_bytes().and_then(|v| read_options(&v))?;
let reserved = reader.read_bytes()?;
let signature_key = reader
.read_bytes()
.and_then(|v| PublicKey::from_bytes(&v))?;
let signed_len = reader.get_offset();
let signature = reader.read_bytes()?;
reader.set_offset(0)?;
let signed_bytes = reader.read_raw_bytes(signed_len)?;
verify_signature(&signature, &signed_bytes, &signature_key)?;
Ok(Certificate {
key_type,
nonce,
key,
serial,
cert_type,
key_id,
principals,
valid_after,
valid_before,
critical_options,
extensions,
reserved,
signature_key,
signature,
comment: None,
serialized: data.to_vec(),
})
}
pub fn standard_extensions() -> HashMap<String, String> {
let mut hm = HashMap::new();
for extension in &STANDARD_EXTENSIONS {
hm.insert(String::from(extension.0), String::from(extension.1));
}
hm
}
pub fn builder(
pubkey: &PublicKey,
cert_type: CertType,
signing_key: &PublicKey,
) -> Result<Certificate> {
let kt_name = pubkey.key_type.as_cert_name();
let key_type = KeyType::from_name(kt_name.as_str())?;
let rng = SystemRandom::new();
let mut nonce = [0x0u8; 32];
match SecureRandom::fill(&rng, &mut nonce) {
Ok(()) => (),
Err(_) => return Err(Error::UnexpectedEof),
};
let mut serial = [0x0u8; 8];
match SecureRandom::fill(&rng, &mut serial) {
Ok(()) => (),
Err(_) => return Err(Error::UnexpectedEof),
};
let serial = u64::from_be_bytes(serial);
Ok(Certificate {
nonce: nonce.to_vec(),
key: pubkey.clone(),
key_type,
serial,
cert_type,
key_id: String::new(),
principals: vec![],
valid_after: 0,
valid_before: 0,
critical_options: HashMap::new(),
extensions: HashMap::new(),
reserved: vec![0, 0, 0, 0, 0, 0, 0, 0],
signature_key: signing_key.clone(),
signature: vec![],
comment: None,
serialized: vec![],
})
}
pub fn serial(mut self, serial: u64) -> Self {
self.serial = serial;
self
}
pub fn key_id<S: AsRef<str>>(mut self, key_id: S) -> Self {
self.key_id = key_id.as_ref().to_owned();
self
}
pub fn principal<S: AsRef<str>>(mut self, principal: S) -> Self {
self.principals.push(principal.as_ref().to_owned());
self
}
pub fn set_principals(mut self, principals: &[String]) -> Self {
self.principals = principals.to_vec();
self
}
pub fn valid_after(mut self, valid_after: u64) -> Self {
self.valid_after = valid_after;
self
}
pub fn valid_before(mut self, valid_before: u64) -> Self {
self.valid_before = valid_before;
self
}
pub fn critical_option<S: AsRef<str>>(mut self, option: S, value: S) -> Self {
self.critical_options
.insert(option.as_ref().to_owned(), value.as_ref().to_owned());
self
}
pub fn set_critical_options(mut self, critical_options: HashMap<String, String>) -> Self {
self.critical_options = critical_options;
self
}
pub fn extension<S: AsRef<str>>(mut self, option: S, value: S) -> Self {
self.extensions
.insert(option.as_ref().to_owned(), value.as_ref().to_owned());
self
}
pub fn set_extensions(mut self, extensions: HashMap<String, String>) -> Self {
self.extensions = extensions;
self
}
pub fn comment<S: AsRef<str>>(mut self, comment: S) -> Self {
self.comment = Some(comment.as_ref().to_owned());
self
}
pub fn tbs_certificate(&self) -> Vec<u8> {
let mut writer = Writer::new();
let kt_name = self.key_type.as_cert_name();
writer.write_string(kt_name.as_str());
writer.write_bytes(&self.nonce);
writer.write_pub_key_data(&self.key);
writer.write_u64(self.serial);
writer.write_u32(self.cert_type as u32);
writer.write_string(&self.key_id);
writer.write_string_vec(&self.principals);
writer.write_u64(self.valid_after);
writer.write_u64(self.valid_before);
writer.write_string_map(&self.critical_options);
writer.write_string_map(&self.extensions);
writer.write_u32(0x0);
writer.write_bytes(&self.signature_key.encode());
writer.as_bytes().to_vec()
}
pub fn add_signature(mut self, signature: &[u8]) -> Result<Self> {
let mut tbs = self.tbs_certificate();
verify_signature(signature, &tbs, &self.signature_key)?;
let mut wrapped_writer = Writer::new();
wrapped_writer.write_bytes(signature);
tbs.extend_from_slice(&wrapped_writer.into_bytes());
self.signature = signature.to_vec();
self.serialized = tbs;
Ok(self)
}
pub fn sign<T: SSHCertificateSigner>(self, signer: &T) -> Result<Self> {
let tbs_certificate = self.tbs_certificate();
let signature = signer.sign(&tbs_certificate).ok_or(Error::SigningError)?;
self.add_signature(&signature)
}
}
fn read_options(buf: &[u8]) -> Result<HashMap<String, String>> {
let mut reader = Reader::new(&buf);
let mut options = HashMap::new();
loop {
let name = match reader.read_string() {
Ok(v) => v,
Err(e) => match e {
Error::UnexpectedEof => break,
_ => return Err(e),
},
};
let value_buf = reader.read_bytes()?;
let value = if !value_buf.is_empty() {
Reader::new(&value_buf).read_string()?
} else {
"".to_string()
};
options.insert(name, value);
}
Ok(options)
}
fn read_principals(buf: &[u8]) -> Result<Vec<String>> {
let mut reader = Reader::new(&buf);
let mut items = Vec::new();
loop {
let principal = match reader.read_string() {
Ok(v) => v,
Err(e) => match e {
Error::UnexpectedEof => break,
_ => return Err(e),
},
};
items.push(principal);
}
Ok(items)
}
fn verify_signature(
signature_buf: &[u8],
signed_bytes: &[u8],
public_key: &PublicKey,
) -> Result<Vec<u8>> {
let mut reader = Reader::new(&signature_buf);
let sig_type = reader.read_string().and_then(|v| KeyType::from_name(&v))?;
if public_key.key_type.kind != sig_type.kind {
return Err(Error::KeyTypeMismatch);
}
match &public_key.kind {
PublicKeyKind::Ecdsa(key) => {
let sig_reader = reader.read_bytes()?;
let mut sig_reader = Reader::new(&sig_reader);
let (alg, len) = match sig_type.name {
"ecdsa-sha2-nistp256" | "sk-ecdsa-sha2-nistp256@openssh.com" => {
(&ECDSA_P256_SHA256_FIXED, 32)
}
"ecdsa-sha2-nistp384" => (&ECDSA_P384_SHA384_FIXED, 48),
_ => return Err(Error::KeyTypeMismatch),
};
let r_bytes = sig_reader.read_positive_mpint()?;
let s_bytes = sig_reader.read_positive_mpint()?;
if r_bytes.len() > len || s_bytes.len() > len {
return Err(Error::InvalidFormat);
}
let mut r = vec![0; len - r_bytes.len()];
let mut s = vec![0; len - s_bytes.len()];
r.extend(r_bytes);
s.extend(s_bytes);
let mut sig = r;
sig.extend(s);
if let Some(sk_application) = &key.sk_application {
let flags = reader.read_raw_bytes(1)?[0];
let signature_counter = reader.read_u32()?;
let mut app_hash = digest::digest(&digest::SHA256, sk_application.as_bytes())
.as_ref()
.to_vec();
let mut data_hash = digest::digest(&digest::SHA256, signed_bytes)
.as_ref()
.to_vec();
app_hash.push(flags);
app_hash.extend_from_slice(&signature_counter.to_be_bytes());
app_hash.append(&mut data_hash);
UnparsedPublicKey::new(alg, &key.key).verify(&app_hash, &sig)?;
} else {
UnparsedPublicKey::new(alg, &key.key).verify(signed_bytes, &sig)?;
}
Ok(signature_buf.to_vec())
}
PublicKeyKind::Rsa(key) => {
let alg = match sig_type.name {
"rsa-sha2-256" => &RSA_PKCS1_2048_8192_SHA256,
"rsa-sha2-512" => &RSA_PKCS1_2048_8192_SHA512,
"ssh-rsa" => &RSA_PKCS1_2048_8192_SHA1_FOR_LEGACY_USE_ONLY,
_ => return Err(Error::KeyTypeMismatch),
};
let signature = reader.read_bytes()?;
let public_key = RsaPublicKeyComponents {
n: &key.n,
e: &key.e,
};
public_key.verify(alg, signed_bytes, &signature)?;
Ok(signature_buf.to_vec())
}
PublicKeyKind::Ed25519(key) => {
match sig_type.name {
"ssh-ed25519" => (),
"sk-ssh-ed25519@openssh.com" => (),
_ => return Err(Error::KeyTypeMismatch),
};
let alg = &ED25519;
let signature = reader.read_bytes()?;
let peer_public_key = UnparsedPublicKey::new(alg, &key.key);
if let Some(sk_application) = &key.sk_application {
let flags = reader.read_raw_bytes(1)?[0];
let signature_counter = reader.read_u32()?;
let mut app_hash = digest::digest(&digest::SHA256, sk_application.as_bytes())
.as_ref()
.to_vec();
let mut data_hash = digest::digest(&digest::SHA256, signed_bytes)
.as_ref()
.to_vec();
app_hash.push(flags);
app_hash.extend_from_slice(&signature_counter.to_be_bytes());
app_hash.append(&mut data_hash);
peer_public_key.verify(&app_hash, &signature)?;
} else {
peer_public_key.verify(signed_bytes, &signature)?;
}
Ok(signature_buf.to_vec())
}
}
}
impl fmt::Display for Certificate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !f.alternate() {
write!(
f,
"{} {} {}",
&self.key_type.name,
base64::encode(&self.serialized),
&self.key_id
)
} else {
let mut pretty: String = format!("Type: {} {}\n", self.key_type, self.cert_type);
pretty.push_str(&format!(
"Public Key: {} {}:{}\n",
self.key_type.short_name,
self.key.fingerprint().kind,
self.key.fingerprint().hash
));
pretty.push_str(&format!(
"Signing CA: {} {}:{} (using {})\n",
self.signature_key.key_type.short_name,
self.signature_key.fingerprint().kind,
self.signature_key.fingerprint().hash,
self.signature_key.key_type
));
pretty.push_str(&format!("Key ID: \"{}\"\n", self.key_id));
pretty.push_str(&format!("Serial: {}\n", self.serial));
if self.valid_before == 0xFFFFFFFFFFFFFFFF && self.valid_after == 0x0 {
pretty.push_str("Valid: forever\n");
} else {
pretty.push_str(&format!(
"Valid between: {} and {}\n",
self.valid_after, self.valid_before
));
}
if self.principals.is_empty() {
pretty.push_str("Principals: (none)\n");
} else {
pretty.push_str("Principals\n");
for principal in &self.principals {
pretty.push_str(&format!("\t{}\n", principal));
}
}
if self.critical_options.is_empty() {
pretty.push_str("Critical Options: (none)\n");
} else {
pretty.push_str("Critical Options:\n");
for (name, value) in &self.critical_options {
pretty.push_str(&format!("\t{} {}\n", name, value));
}
}
if self.extensions.is_empty() {
pretty.push_str("Extensions: (none)\n");
} else {
pretty.push_str("Extensions:\n");
for name in self.extensions.keys() {
pretty.push_str(&format!("\t{}\n", &name));
}
}
write!(f, "{}", pretty)
}
}
}