use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH, Duration};
use chrono::{DateTime, Utc};
use serde::{Serialize, Deserialize};
use crate::{Result, QsshError, PqAlgorithm};
use sha2::{Sha256, Digest};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CertificateType {
User = 1,
Host = 2,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SshCertificate {
pub cert_type: CertificateType,
pub serial: u64,
pub key_id: String,
pub valid_principals: Vec<String>,
pub valid_after: u64,
pub valid_before: u64,
pub critical_options: HashMap<String, String>,
pub extensions: HashMap<String, String>,
pub public_key: PublicKey,
pub signature_key: PublicKey,
pub signature: Vec<u8>,
pub nonce: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PublicKey {
pub algorithm: PqAlgorithm,
pub key_data: Vec<u8>,
pub fingerprint: String,
}
pub struct CertificateAuthority {
private_key: Vec<u8>,
public_key: PublicKey,
algorithm: PqAlgorithm,
serial_counter: u64,
default_lifetime: Duration,
}
impl CertificateAuthority {
pub fn new(algorithm: PqAlgorithm) -> Result<Self> {
let (private_key, public_key_data) = generate_ca_keypair(algorithm)?;
let public_key = PublicKey {
algorithm,
key_data: public_key_data.clone(),
fingerprint: generate_fingerprint(&public_key_data),
};
Ok(Self {
private_key,
public_key,
algorithm,
serial_counter: 1,
default_lifetime: Duration::from_secs(365 * 24 * 3600), })
}
pub fn load(private_key_path: &str, public_key_path: &str) -> Result<Self> {
let private_key = std::fs::read(private_key_path)
.map_err(|e| QsshError::Io(e))?;
let public_key_data = std::fs::read(public_key_path)
.map_err(|e| QsshError::Io(e))?;
let algorithm = detect_algorithm(&public_key_data)?;
let public_key = PublicKey {
algorithm,
key_data: public_key_data.clone(),
fingerprint: generate_fingerprint(&public_key_data),
};
Ok(Self {
private_key,
public_key,
algorithm,
serial_counter: 1,
default_lifetime: Duration::from_secs(365 * 24 * 3600),
})
}
pub fn issue_user_certificate(
&mut self,
user_public_key: Vec<u8>,
key_id: String,
principals: Vec<String>,
options: CertificateOptions,
) -> Result<SshCertificate> {
self.issue_certificate(
CertificateType::User,
user_public_key,
key_id,
principals,
options,
)
}
pub fn issue_host_certificate(
&mut self,
host_public_key: Vec<u8>,
key_id: String,
hostnames: Vec<String>,
options: CertificateOptions,
) -> Result<SshCertificate> {
self.issue_certificate(
CertificateType::Host,
host_public_key,
key_id,
hostnames,
options,
)
}
fn issue_certificate(
&mut self,
cert_type: CertificateType,
public_key_data: Vec<u8>,
key_id: String,
principals: Vec<String>,
options: CertificateOptions,
) -> Result<SshCertificate> {
let serial = self.next_serial();
let now = current_timestamp();
let valid_after = options.valid_after.unwrap_or(now);
let valid_before = options.valid_before
.unwrap_or(now + self.default_lifetime.as_secs());
let public_key = PublicKey {
algorithm: self.algorithm,
key_data: public_key_data.clone(),
fingerprint: generate_fingerprint(&public_key_data),
};
let mut extensions = options.extensions.unwrap_or_default();
if cert_type == CertificateType::User {
apply_default_user_extensions(&mut extensions);
}
let mut cert = SshCertificate {
cert_type,
serial,
key_id,
valid_principals: principals,
valid_after,
valid_before,
critical_options: options.critical_options.unwrap_or_default(),
extensions,
public_key,
signature_key: self.public_key.clone(),
signature: Vec::new(),
nonce: generate_nonce(),
};
cert.signature = self.sign_certificate(&cert)?;
Ok(cert)
}
fn sign_certificate(&self, cert: &SshCertificate) -> Result<Vec<u8>> {
let cert_data = serialize_for_signing(cert)?;
match self.algorithm {
PqAlgorithm::Falcon512 => {
sign_falcon(&cert_data, &self.private_key)
},
PqAlgorithm::SphincsPlus => {
sign_sphincs(&cert_data, &self.private_key)
},
_ => Err(QsshError::Protocol("Unsupported CA algorithm".into())),
}
}
fn next_serial(&mut self) -> u64 {
let serial = self.serial_counter;
self.serial_counter += 1;
serial
}
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
}
#[derive(Debug, Clone, Default)]
pub struct CertificateOptions {
pub valid_after: Option<u64>,
pub valid_before: Option<u64>,
pub critical_options: Option<HashMap<String, String>>,
pub extensions: Option<HashMap<String, String>>,
}
pub struct CertificateValidator {
trusted_cas: Vec<PublicKey>,
revoked_serials: Vec<u64>,
allow_expired: bool,
}
impl CertificateValidator {
pub fn new() -> Self {
Self {
trusted_cas: Vec::new(),
revoked_serials: Vec::new(),
allow_expired: false,
}
}
pub fn add_trusted_ca(&mut self, ca_public_key: PublicKey) {
self.trusted_cas.push(ca_public_key);
}
pub fn revoke_certificate(&mut self, serial: u64) {
self.revoked_serials.push(serial);
}
pub fn validate(&self, cert: &SshCertificate) -> Result<ValidationResult> {
if self.revoked_serials.contains(&cert.serial) {
return Ok(ValidationResult::Revoked);
}
let now = current_timestamp();
if !self.allow_expired {
if now < cert.valid_after {
return Ok(ValidationResult::NotYetValid);
}
if now > cert.valid_before {
return Ok(ValidationResult::Expired);
}
}
let ca_trusted = self.trusted_cas.iter()
.any(|ca| ca.fingerprint == cert.signature_key.fingerprint);
if !ca_trusted {
return Ok(ValidationResult::UntrustedCA);
}
let cert_data = serialize_for_signing(cert)?;
let valid = match cert.signature_key.algorithm {
PqAlgorithm::Falcon512 => {
verify_falcon(&cert_data, &cert.signature, &cert.signature_key.key_data)?
},
PqAlgorithm::SphincsPlus => {
verify_sphincs(&cert_data, &cert.signature, &cert.signature_key.key_data)?
},
_ => false,
};
if valid {
Ok(ValidationResult::Valid)
} else {
Ok(ValidationResult::InvalidSignature)
}
}
pub fn check_principal(&self, cert: &SshCertificate, principal: &str) -> bool {
cert.valid_principals.is_empty() ||
cert.valid_principals.contains(&principal.to_string())
}
pub fn check_critical_options(&self, cert: &SshCertificate, required: &HashMap<String, String>) -> bool {
for (key, value) in required {
match cert.critical_options.get(key) {
Some(v) if v == value => continue,
_ => return false,
}
}
true
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ValidationResult {
Valid,
Revoked,
NotYetValid,
Expired,
UntrustedCA,
InvalidSignature,
}
pub struct CertificateStore {
user_certs: HashMap<String, SshCertificate>,
host_certs: HashMap<String, SshCertificate>,
ca_certs: Vec<PublicKey>,
}
impl CertificateStore {
pub fn new() -> Self {
Self {
user_certs: HashMap::new(),
host_certs: HashMap::new(),
ca_certs: Vec::new(),
}
}
pub fn add_user_cert(&mut self, cert: SshCertificate) {
self.user_certs.insert(cert.key_id.clone(), cert);
}
pub fn add_host_cert(&mut self, hostname: String, cert: SshCertificate) {
self.host_certs.insert(hostname, cert);
}
pub fn add_ca_cert(&mut self, ca_key: PublicKey) {
self.ca_certs.push(ca_key);
}
pub fn get_user_cert(&self, key_id: &str) -> Option<&SshCertificate> {
self.user_certs.get(key_id)
}
pub fn get_host_cert(&self, hostname: &str) -> Option<&SshCertificate> {
self.host_certs.get(hostname)
}
pub fn list_all(&self) -> Vec<CertificateInfo> {
let mut info = Vec::new();
for cert in self.user_certs.values() {
info.push(CertificateInfo::from_cert(cert));
}
for cert in self.host_certs.values() {
info.push(CertificateInfo::from_cert(cert));
}
info
}
}
#[derive(Debug, Clone)]
pub struct CertificateInfo {
pub cert_type: CertificateType,
pub key_id: String,
pub serial: u64,
pub valid_from: DateTime<Utc>,
pub valid_until: DateTime<Utc>,
pub principals: Vec<String>,
pub ca_fingerprint: String,
}
impl CertificateInfo {
fn from_cert(cert: &SshCertificate) -> Self {
Self {
cert_type: cert.cert_type,
key_id: cert.key_id.clone(),
serial: cert.serial,
valid_from: DateTime::from_timestamp(cert.valid_after as i64, 0).unwrap_or_default(),
valid_until: DateTime::from_timestamp(cert.valid_before as i64, 0).unwrap_or_default(),
principals: cert.valid_principals.clone(),
ca_fingerprint: cert.signature_key.fingerprint.clone(),
}
}
}
fn generate_ca_keypair(algorithm: PqAlgorithm) -> Result<(Vec<u8>, Vec<u8>)> {
match algorithm {
PqAlgorithm::Falcon512 => {
use pqcrypto_falcon::falcon512;
use pqcrypto_traits::sign::{SecretKey, PublicKey};
let (pk, sk) = falcon512::keypair();
Ok((sk.as_bytes().to_vec(), pk.as_bytes().to_vec()))
},
PqAlgorithm::SphincsPlus => {
use pqcrypto_sphincsplus::sphincssha256128ssimple as sphincs;
use pqcrypto_traits::sign::{SecretKey, PublicKey};
let (pk, sk) = sphincs::keypair();
Ok((sk.as_bytes().to_vec(), pk.as_bytes().to_vec()))
},
_ => Err(QsshError::Protocol("Unsupported algorithm for CA".into())),
}
}
fn detect_algorithm(public_key: &[u8]) -> Result<PqAlgorithm> {
match public_key.len() {
897 => Ok(PqAlgorithm::Falcon512),
32 => Ok(PqAlgorithm::SphincsPlus),
_ => Ok(PqAlgorithm::Falcon512), }
}
fn generate_fingerprint(data: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(data);
let hash = hasher.finalize();
format!("SHA256:{}", base64::Engine::encode(&base64::engine::general_purpose::STANDARD, hash))
}
fn generate_nonce() -> Vec<u8> {
use rand::Rng;
let mut rng = rand::thread_rng();
(0..32).map(|_| rng.gen()).collect()
}
fn current_timestamp() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
}
fn serialize_for_signing(cert: &SshCertificate) -> Result<Vec<u8>> {
let mut cert_copy = cert.clone();
cert_copy.signature.clear();
bincode::serialize(&cert_copy)
.map_err(|e| QsshError::Protocol(format!("Failed to serialize certificate: {}", e)))
}
fn sign_falcon(data: &[u8], private_key: &[u8]) -> Result<Vec<u8>> {
use pqcrypto_falcon::falcon512;
use pqcrypto_traits::sign::{SecretKey, SignedMessage};
let sk = falcon512::SecretKey::from_bytes(private_key)
.map_err(|_| QsshError::Crypto("Invalid Falcon private key".into()))?;
let signature = falcon512::sign(data, &sk);
Ok(signature.as_bytes().to_vec())
}
fn verify_falcon(data: &[u8], signature: &[u8], public_key: &[u8]) -> Result<bool> {
use pqcrypto_falcon::falcon512;
use pqcrypto_traits::sign::{PublicKey as PubKey, DetachedSignature};
let pk = falcon512::PublicKey::from_bytes(public_key)
.map_err(|_| QsshError::Crypto("Invalid Falcon public key".into()))?;
let sig = falcon512::DetachedSignature::from_bytes(signature)
.map_err(|_| QsshError::Crypto("Invalid Falcon signature".into()))?;
Ok(falcon512::verify_detached_signature(&sig, data, &pk).is_ok())
}
fn sign_sphincs(data: &[u8], private_key: &[u8]) -> Result<Vec<u8>> {
use pqcrypto_sphincsplus::sphincssha256128ssimple as sphincs;
use pqcrypto_traits::sign::{SecretKey, SignedMessage};
let sk = sphincs::SecretKey::from_bytes(private_key)
.map_err(|_| QsshError::Crypto("Invalid SPHINCS+ private key".into()))?;
let signature = sphincs::sign(data, &sk);
Ok(signature.as_bytes().to_vec())
}
fn verify_sphincs(data: &[u8], signature: &[u8], public_key: &[u8]) -> Result<bool> {
use pqcrypto_sphincsplus::sphincssha256128ssimple as sphincs;
use pqcrypto_traits::sign::{PublicKey as PubKey, DetachedSignature};
let pk = sphincs::PublicKey::from_bytes(public_key)
.map_err(|_| QsshError::Crypto("Invalid SPHINCS+ public key".into()))?;
let sig = sphincs::DetachedSignature::from_bytes(signature)
.map_err(|_| QsshError::Crypto("Invalid SPHINCS+ signature".into()))?;
Ok(sphincs::verify_detached_signature(&sig, data, &pk).is_ok())
}
fn apply_default_user_extensions(extensions: &mut HashMap<String, String>) {
extensions.entry("permit-X11-forwarding".to_string())
.or_insert("".to_string());
extensions.entry("permit-agent-forwarding".to_string())
.or_insert("".to_string());
extensions.entry("permit-port-forwarding".to_string())
.or_insert("".to_string());
extensions.entry("permit-pty".to_string())
.or_insert("".to_string());
extensions.entry("permit-user-rc".to_string())
.or_insert("".to_string());
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ca_creation() {
let ca = CertificateAuthority::new(PqAlgorithm::Falcon512).unwrap();
assert_eq!(ca.algorithm, PqAlgorithm::Falcon512);
assert!(!ca.public_key.fingerprint.is_empty());
}
#[test]
fn test_certificate_issuance() {
let mut ca = CertificateAuthority::new(PqAlgorithm::Falcon512).unwrap();
let user_key = vec![1, 2, 3, 4, 5];
let cert = ca.issue_user_certificate(
user_key,
"user@example.com".to_string(),
vec!["alice".to_string(), "bob".to_string()],
CertificateOptions::default(),
).unwrap();
assert_eq!(cert.cert_type, CertificateType::User);
assert_eq!(cert.key_id, "user@example.com");
assert_eq!(cert.valid_principals, vec!["alice", "bob"]);
assert!(!cert.signature.is_empty());
}
#[test]
fn test_certificate_validation() {
let mut ca = CertificateAuthority::new(PqAlgorithm::Falcon512).unwrap();
let mut validator = CertificateValidator::new();
validator.add_trusted_ca(ca.public_key().clone());
let user_key = vec![5, 4, 3, 2, 1];
let cert = ca.issue_user_certificate(
user_key,
"test@example.com".to_string(),
vec!["testuser".to_string()],
CertificateOptions::default(),
).unwrap();
let result = validator.validate(&cert).unwrap();
assert_eq!(result, ValidationResult::Valid);
assert!(validator.check_principal(&cert, "testuser"));
assert!(!validator.check_principal(&cert, "otheruser"));
}
#[test]
fn test_certificate_expiration() {
let mut ca = CertificateAuthority::new(PqAlgorithm::Falcon512).unwrap();
let validator = CertificateValidator::new();
let past = current_timestamp() - 3600;
let options = CertificateOptions {
valid_after: Some(past - 7200),
valid_before: Some(past),
..Default::default()
};
let cert = ca.issue_user_certificate(
vec![1, 2, 3],
"expired@example.com".to_string(),
vec!["user".to_string()],
options,
).unwrap();
let result = validator.validate(&cert).unwrap();
assert_eq!(result, ValidationResult::Expired);
}
}