use std::borrow::Cow;
use ssh_key::{Certificate, PublicKey};
pub mod client;
mod msg;
pub mod server;
#[derive(Debug, PartialEq, Eq)]
pub enum Constraint {
KeyLifetime { seconds: u32 },
Confirm,
Extensions { name: Vec<u8>, details: Vec<u8> },
}
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum AgentIdentity {
PublicKey {
key: PublicKey,
comment: String,
},
Certificate {
certificate: Certificate,
comment: String,
},
}
impl From<PublicKey> for AgentIdentity {
fn from(key: PublicKey) -> Self {
Self::PublicKey {
key,
comment: String::new(),
}
}
}
impl From<Certificate> for AgentIdentity {
fn from(certificate: Certificate) -> Self {
Self::Certificate {
certificate,
comment: String::new(),
}
}
}
impl AgentIdentity {
pub fn public_key(&self) -> Cow<'_, PublicKey> {
match self {
Self::PublicKey { key, .. } => Cow::Borrowed(key),
Self::Certificate { certificate, .. } => {
Cow::Owned(PublicKey::new(certificate.public_key().clone(), ""))
}
}
}
pub fn comment(&self) -> &str {
match self {
Self::PublicKey { comment, .. } => comment,
Self::Certificate { comment, .. } => comment,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ssh_key::{PrivateKey, certificate};
fn create_test_certificate() -> Certificate {
use std::time::{SystemTime, UNIX_EPOCH};
let ca_key = PrivateKey::random(&mut rand::rng(), ssh_key::Algorithm::Ed25519).unwrap();
let user_key = PrivateKey::random(&mut rand::rng(), ssh_key::Algorithm::Ed25519).unwrap();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let valid_after = now - 3600; let valid_before = now + 86400 * 365;
let mut builder = certificate::Builder::new_with_random_nonce(
&mut rand::rng(),
user_key.public_key(),
valid_after,
valid_before,
)
.unwrap();
builder.serial(1).unwrap();
builder.key_id("test-cert").unwrap();
builder.cert_type(certificate::CertType::User).unwrap();
builder.valid_principal("testuser").unwrap();
builder.sign(&ca_key).unwrap()
}
#[test]
fn test_agent_identity_public_key_variant() {
let key = PrivateKey::random(&mut rand::rng(), ssh_key::Algorithm::Ed25519)
.unwrap()
.public_key()
.clone();
let comment = "test-key-comment".to_string();
let identity = AgentIdentity::PublicKey {
key: key.clone(),
comment: comment.clone(),
};
let retrieved_key = identity.public_key();
assert!(matches!(retrieved_key, Cow::Borrowed(_)));
assert_eq!(retrieved_key.key_data(), key.key_data());
assert_eq!(identity.comment(), "test-key-comment");
}
#[test]
fn test_agent_identity_certificate_variant() {
let cert = create_test_certificate();
let comment = "test-cert-comment".to_string();
let identity = AgentIdentity::Certificate {
certificate: cert.clone(),
comment: comment.clone(),
};
let retrieved_key = identity.public_key();
assert!(matches!(retrieved_key, Cow::Owned(_)));
assert_eq!(retrieved_key.key_data(), cert.public_key());
assert_eq!(identity.comment(), "test-cert-comment");
}
#[test]
fn test_agent_identity_clone() {
let key = PrivateKey::random(&mut rand::rng(), ssh_key::Algorithm::Ed25519)
.unwrap()
.public_key()
.clone();
let identity = AgentIdentity::PublicKey {
key,
comment: "cloneable".to_string(),
};
let cloned = identity.clone();
assert_eq!(cloned.comment(), identity.comment());
}
#[test]
fn test_agent_identity_debug() {
let key = PrivateKey::random(&mut rand::rng(), ssh_key::Algorithm::Ed25519)
.unwrap()
.public_key()
.clone();
let identity = AgentIdentity::PublicKey {
key,
comment: "debug-test".to_string(),
};
let debug_str = format!("{:?}", identity);
assert!(debug_str.contains("PublicKey"));
}
}