use std::collections::HashMap;
use std::fmt;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use ring::signature::{
ECDSA_P256_SHA256_FIXED,
ECDSA_P384_SHA384_FIXED,
RSA_PKCS1_2048_8192_SHA256,
RSA_PKCS1_2048_8192_SHA512,
ED25519,
UnparsedPublicKey,
RsaPublicKeyComponents};
use ring::rand::{SystemRandom, SecureRandom};
use super::error::{Error, ErrorKind, Result};
use super::keytype::{KeyType};
use super::pubkey::{PublicKey, PublicKeyKind};
use super::reader::Reader;
use std::convert::TryFrom;
#[derive(Debug, PartialEq, 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", ""),
];
impl From<Extensions> for HashMap<String, String> {
fn from(extensions: Extensions) -> Self {
match extensions {
Extensions::Standard => {
let mut hm = HashMap::new();
for extension in &STANDARD_EXTENSIONS {
hm.insert(String::from(extension.0), String::from(extension.1));
}
hm
},
Extensions::Custom(co) => co,
}
}
}
#[derive(Debug)]
pub enum Extensions {
Standard,
Custom(HashMap<String, String>)
}
#[derive(Debug)]
pub enum CriticalOptions {
None,
Custom(HashMap<String, String>)
}
impl From<CriticalOptions> for HashMap<String, String> {
fn from(critical_options: CriticalOptions) -> Self {
match critical_options {
CriticalOptions::None => HashMap::new(),
CriticalOptions::Custom(co) => co,
}
}
}
#[derive(Debug)]
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 kt_name = iter
.next()
.ok_or_else(|| Error::with_kind(ErrorKind::InvalidFormat))?;
let key_type = KeyType::from_name(&kt_name)?;
if !key_type.is_cert {
return Err(Error::with_kind(ErrorKind::NotCertificate));
}
let data = iter
.next()
.ok_or_else(|| Error::with_kind(ErrorKind::InvalidFormat))?;
let comment = iter.next().map(String::from);
let decoded = base64::decode(&data)?;
let mut reader = Reader::new(&decoded);
let kt_from_reader = reader.read_string()?;
if kt_name != kt_from_reader {
return Err(Error::with_kind(ErrorKind::KeyTypeMismatch));
}
let nonce = reader.read_bytes()?;
let key = PublicKey::from_reader(&kt_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::with_kind(ErrorKind::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).unwrap();
let signed_bytes = reader.read_raw_bytes(signed_len).unwrap();
verify_signature(&signature, &signed_bytes, &signature_key)?;
let cert = Certificate {
key_type,
nonce,
key,
serial,
cert_type,
key_id,
principals,
valid_after,
valid_before,
critical_options,
extensions,
reserved,
signature_key,
signature,
comment,
serialized: decoded,
};
Ok(cert)
}
#[allow(clippy::too_many_arguments)]
pub fn new(
pubkey: PublicKey,
cert_type: CertType,
serial: u64,
key_id: String,
principals: Vec<String>,
valid_after: u64,
valid_before: u64,
critical_options: CriticalOptions,
extensions: Extensions,
ca_pubkey: PublicKey,
signer: impl Fn(&[u8]) -> Option<Vec<u8>>,
) -> Result<Certificate> {
let mut writer = super::Writer::new();
let kt_name = format!("{}-cert-v01@openssh.com", pubkey.key_type.name);
writer.write_string(kt_name.as_str());
let mut nonce = [0x0u8; 32];
let rng = SystemRandom::new();
match SecureRandom::fill(&rng, &mut nonce) {
Ok(()) => (),
Err(_) => return Err(Error::with_kind(ErrorKind::UnexpectedEof)),
};
writer.write_bytes(&nonce);
writer.write_pub_key(&pubkey);
writer.write_u64(serial);
writer.write_u32(cert_type as u32);
writer.write_string(&key_id);
writer.write_string_vec(&principals);
writer.write_u64(valid_after);
writer.write_u64(valid_before);
let critical_options = match critical_options {
CriticalOptions::None => {
writer.write_string_map(&HashMap::new());
HashMap::new()
},
CriticalOptions::Custom(co) => {
writer.write_string_map(&co);
co
},
};
let extensions = match extensions {
Extensions::Standard => {
let stdex = STANDARD_EXTENSIONS.iter().map(|x| (String::from(x.0), String::from(x.1))).collect();
writer.write_string_map(&stdex);
stdex
},
Extensions::Custom(co) => {
writer.write_string_map(&co);
co
},
};
writer.write_u32(0x0);
writer.write_bytes(&ca_pubkey.encode());
let signature = match signer(writer.as_bytes()) {
Some(sig) => sig,
None => return Err(Error::with_kind(ErrorKind::SigningError)),
};
match verify_signature(&signature, &writer.as_bytes(), &ca_pubkey) {
Ok(_) => (),
Err(e) => return Err(e),
}
writer.write_bytes(&signature);
Ok(Certificate {
key_type: KeyType::from_name(kt_name.as_str()).unwrap(),
nonce: nonce.to_vec(),
key: pubkey,
serial,
cert_type,
key_id,
principals,
valid_after,
valid_before,
critical_options,
extensions,
reserved: vec![0,0,0,0,0,0,0,0],
signature_key: ca_pubkey,
signature,
comment: None,
serialized: writer.into_bytes(),
})
}
}
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.kind {
ErrorKind::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.kind {
ErrorKind::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))?;
match &public_key.kind {
PublicKeyKind::Ecdsa(key) => {
let sig_reader = reader.read_bytes()?;
let mut reader = Reader::new(&sig_reader);
let mut sig = reader.read_mpint()?;
sig.extend(reader.read_mpint()?);
let alg = match sig_type.name {
"ecdsa-sha2-nistp256" => &ECDSA_P256_SHA256_FIXED,
"ecdsa-sha2-nistp384" => &ECDSA_P384_SHA384_FIXED,
_ => return Err(Error::with_kind(ErrorKind::KeyTypeMismatch)),
};
let result = UnparsedPublicKey::new(alg, &key.key).verify(&signed_bytes, &sig);
match result {
Ok(()) => Ok(signature_buf.to_vec()),
Err(_) => Err(Error::with_kind(ErrorKind::CertificateInvalidSignature)),
}
},
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,
_ => return Err(Error::with_kind(ErrorKind::KeyTypeMismatch)),
};
let signature = reader.read_bytes()?;
let public_key = RsaPublicKeyComponents { n: &key.n, e: &key.e };
let result = public_key.verify(alg, &signed_bytes, &signature);
match result {
Ok(()) => Ok(signature_buf.to_vec()),
Err(e) => {
println!("Error: {}", e);
Err(Error::with_kind(ErrorKind::CertificateInvalidSignature))
}
}
},
PublicKeyKind::Ed25519(key) => {
let alg = &ED25519;
let signature = reader.read_bytes()?;
let peer_public_key = UnparsedPublicKey::new(alg, &key.key);
match peer_public_key.verify(&signed_bytes, &signature) {
Ok(()) => Ok(signature_buf.to_vec()),
Err(e) => {
println!("Error: {}", e);
Err(Error::with_kind(ErrorKind::CertificateInvalidSignature))
}
}
},
}
}
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 {
writeln!(f, "Type: {} {}", self.key_type, self.cert_type).unwrap();
writeln!(f, "Public Key: {} {}:{}", self.key_type.short_name, self.key.fingerprint().kind, self.key.fingerprint().hash).unwrap();
writeln!(f, "Signing CA: {} {}:{} (using {})", self.signature_key.key_type.short_name, self.signature_key.fingerprint().kind, self.signature_key.fingerprint().hash, self.signature_key.key_type).unwrap();
writeln!(f, "Key ID: \"{}\"", self.key_id).unwrap();
writeln!(f, "Serial: {}", self.serial).unwrap();
if self.valid_before == 0xFFFFFFFFFFFFFFFF && self.valid_after == 0x0 {
writeln!(f, "Valid: forever").unwrap();
} else {
writeln!(f, "Valid between: {} and {}", self.valid_after, self.valid_before).unwrap();
}
if self.principals.is_empty() {
writeln!(f, "Principals: (none)").unwrap();
} else {
writeln!(f, "Principals:").unwrap();
for principal in &self.principals {
writeln!(f, "\t{}", principal).unwrap();
}
}
if self.critical_options.is_empty() {
writeln!(f, "Critical Options: (none)").unwrap();
} else {
writeln!(f, "Critical Options:").unwrap();
for (name, value) in &self.critical_options {
writeln!(f, "\t{} {}", name, value).unwrap();
}
}
if self.extensions.is_empty() {
writeln!(f, "Extensions: (none)").unwrap();
} else {
writeln!(f, "Extensions:").unwrap();
for name in self.extensions.keys() {
writeln!(f, "\t{}", name).unwrap();
}
}
write!(f, "")
}
}
}