use crate::crypt::hash::hash_public_key;
use crate::crypt::{CryptoSigningAlgorithm, detect_algorithm_from_public_key};
use crate::error::JacsError;
use crate::paths::trust_store_dir;
use crate::schema::utils::ValueExt;
use crate::time_utils;
use base64::{Engine as _, engine::general_purpose::STANDARD as B64};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fs;
use std::str::FromStr;
use tracing::{info, warn};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrustedAgent {
pub agent_id: String,
pub name: Option<String>,
pub public_key_pem: String,
pub public_key_hash: String,
pub trusted_at: String,
}
#[must_use = "trust operation result must be checked for errors"]
pub fn trust_agent(agent_json: &str) -> Result<String, JacsError> {
trust_agent_with_key(agent_json, None)
}
#[must_use = "trust operation result must be checked for errors"]
pub fn trust_agent_with_key(agent_json: &str, public_key_pem: Option<&str>) -> Result<String, JacsError> {
let agent_value: Value = serde_json::from_str(agent_json).map_err(|e| {
JacsError::DocumentMalformed {
field: "agent_json".to_string(),
reason: e.to_string(),
}
})?;
let agent_id = agent_value.get_str_required("jacsId")?;
let name = agent_value.get_str("name");
let public_key_hash = agent_value.get_path_str_required(&["jacsSignature", "publicKeyHash"])?;
let signing_algorithm = agent_value.get_path_str(&["jacsSignature", "signingAlgorithm"]);
if signing_algorithm.is_none() {
warn!(
agent_id = %agent_id,
"SECURITY WARNING: Agent signature missing signingAlgorithm field. \
Falling back to algorithm detection. This is insecure and deprecated. \
Re-sign this agent document to include the signingAlgorithm field."
);
}
let public_key_bytes: Vec<u8> = match public_key_pem {
Some(pem) => pem.as_bytes().to_vec(),
None => {
load_public_key_from_cache(&public_key_hash)?
}
};
let computed_hash = hash_public_key(public_key_bytes.clone());
if computed_hash != public_key_hash {
return Err(JacsError::SignatureVerificationFailed {
reason: format!(
"Public key hash mismatch for agent '{}': the provided public key (hash: '{}') \
does not match the key hash in the agent's signature (expected: '{}'). \
This could mean: (1) the wrong public key was provided, \
(2) the agent document was modified after signing, or \
(3) the agent's keys have been rotated. \
Verify you have the correct public key for this agent.",
agent_id, computed_hash, public_key_hash
),
});
}
verify_agent_self_signature(&agent_value, &public_key_bytes, signing_algorithm.as_deref())?;
let trust_dir = trust_store_dir();
fs::create_dir_all(&trust_dir).map_err(|e| JacsError::DirectoryCreateFailed {
path: trust_dir.to_string_lossy().to_string(),
reason: e.to_string(),
})?;
let agent_file = trust_dir.join(format!("{}.json", agent_id));
fs::write(&agent_file, agent_json).map_err(|e| JacsError::FileWriteFailed {
path: agent_file.to_string_lossy().to_string(),
reason: e.to_string(),
})?;
save_public_key_to_cache(&public_key_hash, &public_key_bytes, signing_algorithm.as_deref())?;
let public_key_pem_string = String::from_utf8(public_key_bytes.clone())
.unwrap_or_else(|_| B64.encode(&public_key_bytes));
let trusted_agent = TrustedAgent {
agent_id: agent_id.clone(),
name,
public_key_pem: public_key_pem_string,
public_key_hash,
trusted_at: time_utils::now_rfc3339(),
};
let metadata_file = trust_dir.join(format!("{}.meta.json", agent_id));
let metadata_json = serde_json::to_string_pretty(&trusted_agent).map_err(|e| {
JacsError::Internal {
message: format!("Failed to serialize metadata: {}", e),
}
})?;
fs::write(&metadata_file, metadata_json).map_err(|e| JacsError::Internal {
message: format!("Failed to write metadata file: {}", e),
})?;
info!("Trusted agent {} added to trust store", agent_id);
Ok(agent_id)
}
#[must_use = "list of trusted agents must be used"]
pub fn list_trusted_agents() -> Result<Vec<String>, JacsError> {
let trust_dir = trust_store_dir();
if !trust_dir.exists() {
return Ok(Vec::new());
}
let mut agents = Vec::new();
let entries = fs::read_dir(&trust_dir).map_err(|e| JacsError::Internal {
message: format!("Failed to read trust store directory: {}", e),
})?;
for entry in entries {
let entry = entry.map_err(|e| JacsError::Internal {
message: format!("Failed to read directory entry: {}", e),
})?;
let path = entry.path();
if path.extension().is_some_and(|ext| ext == "json")
&& !path
.file_name()
.and_then(|n| n.to_str())
.is_some_and(|n| n.ends_with(".meta.json"))
&& let Some(stem) = path.file_stem().and_then(|s| s.to_str())
{
agents.push(stem.to_string());
}
}
Ok(agents)
}
#[must_use = "untrust operation result must be checked for errors"]
pub fn untrust_agent(agent_id: &str) -> Result<(), JacsError> {
let trust_dir = trust_store_dir();
let agent_file = trust_dir.join(format!("{}.json", agent_id));
let metadata_file = trust_dir.join(format!("{}.meta.json", agent_id));
if !agent_file.exists() {
return Err(JacsError::AgentNotTrusted {
agent_id: agent_id.to_string(),
});
}
if agent_file.exists() {
fs::remove_file(&agent_file).map_err(|e| JacsError::Internal {
message: format!("Failed to remove agent file: {}", e),
})?;
}
if metadata_file.exists() {
fs::remove_file(&metadata_file).map_err(|e| JacsError::Internal {
message: format!("Failed to remove metadata file: {}", e),
})?;
}
info!("Agent {} removed from trust store", agent_id);
Ok(())
}
#[must_use = "trusted agent data must be used"]
pub fn get_trusted_agent(agent_id: &str) -> Result<String, JacsError> {
let trust_dir = trust_store_dir();
let agent_file = trust_dir.join(format!("{}.json", agent_id));
if !agent_file.exists() {
return Err(JacsError::TrustError(format!(
"Agent '{}' is not in the trust store. Use trust_agent() or trust_agent_with_key() \
to add the agent first. Use list_trusted_agents() to see currently trusted agents. \
Expected file at: {}",
agent_id,
agent_file.to_string_lossy()
)));
}
fs::read_to_string(&agent_file).map_err(|e| JacsError::FileReadFailed {
path: agent_file.to_string_lossy().to_string(),
reason: e.to_string(),
})
}
#[must_use = "public key hash must be used"]
pub fn get_trusted_public_key_hash(agent_id: &str) -> Result<String, JacsError> {
let agent_json = get_trusted_agent(agent_id)?;
let agent_value: Value = serde_json::from_str(&agent_json).map_err(|e| {
JacsError::DocumentMalformed {
field: "agent_json".to_string(),
reason: e.to_string(),
}
})?;
agent_value.get_path_str_required(&["jacsSignature", "publicKeyHash"])
}
pub fn is_trusted(agent_id: &str) -> bool {
let trust_dir = trust_store_dir();
let agent_file = trust_dir.join(format!("{}.json", agent_id));
agent_file.exists()
}
fn load_public_key_from_cache(public_key_hash: &str) -> Result<Vec<u8>, JacsError> {
let trust_dir = trust_store_dir();
let keys_dir = trust_dir.join("keys");
let key_file = keys_dir.join(format!("{}.pem", public_key_hash));
if !key_file.exists() {
return Err(JacsError::TrustError(format!(
"Public key not found in trust store cache for hash '{}'. \
To trust this agent, call trust_agent_with_key() and provide the agent's public key PEM. \
Expected key at: {}",
public_key_hash,
key_file.to_string_lossy()
)));
}
fs::read(&key_file).map_err(|e| JacsError::FileReadFailed {
path: key_file.to_string_lossy().to_string(),
reason: e.to_string(),
})
}
fn save_public_key_to_cache(
public_key_hash: &str,
public_key_bytes: &[u8],
algorithm: Option<&str>,
) -> Result<(), JacsError> {
let trust_dir = trust_store_dir();
let keys_dir = trust_dir.join("keys");
fs::create_dir_all(&keys_dir).map_err(|e| JacsError::DirectoryCreateFailed {
path: keys_dir.to_string_lossy().to_string(),
reason: e.to_string(),
})?;
let key_file = keys_dir.join(format!("{}.pem", public_key_hash));
fs::write(&key_file, public_key_bytes).map_err(|e| JacsError::FileWriteFailed {
path: key_file.to_string_lossy().to_string(),
reason: e.to_string(),
})?;
if let Some(algo) = algorithm {
let algo_file = keys_dir.join(format!("{}.algo", public_key_hash));
fs::write(&algo_file, algo).map_err(|e| JacsError::FileWriteFailed {
path: algo_file.to_string_lossy().to_string(),
reason: e.to_string(),
})?;
}
Ok(())
}
fn validate_signature_timestamp(timestamp_str: &str) -> Result<(), JacsError> {
time_utils::validate_signature_timestamp(timestamp_str)
}
fn verify_agent_self_signature(
agent_value: &Value,
public_key_bytes: &[u8],
algorithm: Option<&str>,
) -> Result<(), JacsError> {
let signature_date = agent_value.get_path_str_required(&["jacsSignature", "date"])?;
validate_signature_timestamp(&signature_date)?;
let signature_b64 = agent_value.get_path_str_required(&["jacsSignature", "signature"])?;
let fields = agent_value.get_path_array_required(&["jacsSignature", "fields"])?;
let mut content_parts: Vec<String> = Vec::new();
for field in fields {
if let Some(field_name) = field.as_str()
&& let Some(value) = agent_value.get(field_name)
&& let Some(str_val) = value.as_str()
{
content_parts.push(str_val.to_string());
}
}
let signed_content = content_parts.join(" ");
let algo = match algorithm {
Some(a) => CryptoSigningAlgorithm::from_str(a).map_err(|_| JacsError::SignatureVerificationFailed {
reason: format!(
"Unknown signing algorithm '{}'. Supported algorithms are: \
'ring-Ed25519', 'RSA-PSS', 'pq-dilithium', 'pq-dilithium-alt', 'pq2025'. \
The agent document may have been signed with an unsupported algorithm version.",
a
),
})?,
None => {
detect_algorithm_from_public_key(public_key_bytes).map_err(|e| {
JacsError::SignatureVerificationFailed {
reason: format!(
"Could not detect signing algorithm from public key: {}. \
The agent document is missing the 'signingAlgorithm' field and \
automatic detection failed. Re-sign the agent document to include \
the signingAlgorithm field, or verify the public key format is correct.",
e
),
}
})?
}
};
let verification_result = match algo {
CryptoSigningAlgorithm::RsaPss => {
crate::crypt::rsawrapper::verify_string(
public_key_bytes.to_vec(),
&signed_content,
&signature_b64,
)
}
CryptoSigningAlgorithm::RingEd25519 => {
crate::crypt::ringwrapper::verify_string(
public_key_bytes.to_vec(),
&signed_content,
&signature_b64,
)
}
CryptoSigningAlgorithm::PqDilithium | CryptoSigningAlgorithm::PqDilithiumAlt => {
crate::crypt::pq::verify_string(
public_key_bytes.to_vec(),
&signed_content,
&signature_b64,
)
}
CryptoSigningAlgorithm::Pq2025 => {
crate::crypt::pq2025::verify_string(
public_key_bytes.to_vec(),
&signed_content,
&signature_b64,
)
}
};
verification_result.map_err(|e| JacsError::SignatureVerificationFailed {
reason: format!(
"Cryptographic signature verification failed using {} algorithm: {}. \
This typically means: (1) the agent document was modified after signing, \
(2) the wrong public key is being used, or (3) the signature is corrupted. \
Verify the agent document integrity and ensure the correct public key is provided.",
algo, e
),
})?;
info!("Agent self-signature verified successfully");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::time_utils::{now_rfc3339, now_utc};
use serial_test::serial;
use std::env;
use tempfile::TempDir;
struct TrustTestGuard {
_temp_dir: TempDir,
original_home: Option<String>,
}
impl TrustTestGuard {
fn new() -> Self {
let original_home = env::var("HOME").ok();
let temp_dir = TempDir::new().expect("Failed to create temp directory for test");
unsafe {
env::set_var("HOME", temp_dir.path().to_str().unwrap());
}
Self {
_temp_dir: temp_dir,
original_home,
}
}
}
impl Drop for TrustTestGuard {
fn drop(&mut self) {
unsafe {
match &self.original_home {
Some(home) => env::set_var("HOME", home),
None => env::remove_var("HOME"),
}
}
}
}
fn setup_test_trust_dir() -> TrustTestGuard {
TrustTestGuard::new()
}
#[test]
fn test_valid_recent_timestamp() {
let now = now_rfc3339();
let result = validate_signature_timestamp(&now);
assert!(result.is_ok(), "Recent timestamp should be valid: {:?}", result);
}
#[test]
fn test_valid_past_timestamp() {
let past = (now_utc() - chrono::Duration::hours(1)).to_rfc3339();
let result = validate_signature_timestamp(&past);
assert!(result.is_ok(), "Past timestamp should be valid when expiration is disabled: {:?}", result);
}
#[test]
fn test_valid_old_timestamp() {
let old = (now_utc() - chrono::Duration::days(365)).to_rfc3339();
let result = validate_signature_timestamp(&old);
assert!(result.is_ok(), "Old timestamp should be valid when expiration is disabled: {:?}", result);
}
#[test]
fn test_timestamp_slight_future_allowed() {
let slight_future = (now_utc() + chrono::Duration::seconds(30)).to_rfc3339();
let result = validate_signature_timestamp(&slight_future);
assert!(result.is_ok(), "Slight future timestamp should be allowed for clock drift: {:?}", result);
}
#[test]
fn test_timestamp_far_future_rejected() {
let far_future = (now_utc() + chrono::Duration::minutes(10)).to_rfc3339();
let result = validate_signature_timestamp(&far_future);
assert!(result.is_err(), "Far future timestamp should be rejected");
if let Err(JacsError::SignatureVerificationFailed { reason }) = result {
assert!(
reason.contains("future"),
"Error should mention future timestamp: {}",
reason
);
} else {
panic!("Expected SignatureVerificationFailed error");
}
}
#[test]
fn test_timestamp_invalid_format_rejected() {
let invalid_timestamps = [
"not-a-timestamp",
"2024-13-01T00:00:00Z", "2024-01-32T00:00:00Z", "01/01/2024", "", ];
for invalid in invalid_timestamps {
let result = validate_signature_timestamp(invalid);
assert!(
result.is_err(),
"Invalid timestamp '{}' should be rejected",
invalid
);
if let Err(JacsError::SignatureVerificationFailed { reason }) = result {
assert!(
reason.contains("Invalid") || reason.contains("format"),
"Error should mention invalid format for '{}': {}",
invalid,
reason
);
}
}
}
#[test]
fn test_timestamp_various_valid_formats() {
let valid_timestamps = [
"2024-01-15T10:30:00Z",
"2024-06-20T15:45:30+00:00",
"2024-12-01T00:00:00.000Z",
];
for valid in valid_timestamps {
let result = validate_signature_timestamp(valid);
assert!(
result.is_ok(),
"Valid timestamp format '{}' should be accepted: {:?}",
valid,
result
);
}
}
#[test]
#[serial]
fn test_list_empty_trust_store() {
let _temp = setup_test_trust_dir();
let agents = list_trusted_agents().unwrap();
assert!(agents.is_empty());
}
#[test]
#[serial]
fn test_is_trusted_unknown() {
let _temp = setup_test_trust_dir();
assert!(!is_trusted("unknown-agent-id"));
}
#[test]
#[serial]
fn test_trust_agent_rejects_missing_signature() {
let _temp = setup_test_trust_dir();
let agent_json = r#"{
"jacsId": "test-agent-id",
"name": "Test Agent"
}"#;
let result = trust_agent(agent_json);
assert!(result.is_err());
match result {
Err(JacsError::DocumentMalformed { field, .. }) => {
assert!(field.contains("publicKeyHash"));
}
_ => panic!("Expected DocumentMalformed error"),
}
}
#[test]
#[serial]
fn test_trust_agent_rejects_invalid_public_key_hash() {
let _temp = setup_test_trust_dir();
let agent_json = r#"{
"jacsId": "test-agent-id",
"name": "Test Agent",
"jacsSignature": {
"agentID": "test-agent-id",
"agentVersion": "v1",
"date": "2024-01-01T00:00:00Z",
"signature": "dGVzdHNpZw==",
"publicKeyHash": "wrong-hash",
"signingAlgorithm": "ring-Ed25519",
"fields": ["name"]
}
}"#;
let result = trust_agent(agent_json);
assert!(result.is_err());
}
#[test]
#[serial]
fn test_save_and_load_public_key_cache() {
let _temp = setup_test_trust_dir();
let test_key = b"test-public-key-content";
let hash = "test-hash-123";
let save_result = save_public_key_to_cache(hash, test_key, Some("ring-Ed25519"));
assert!(save_result.is_ok());
let load_result = load_public_key_from_cache(hash);
assert!(load_result.is_ok());
assert_eq!(load_result.unwrap(), test_key);
}
#[test]
#[serial]
fn test_load_missing_public_key_cache() {
let _temp = setup_test_trust_dir();
let result = load_public_key_from_cache("nonexistent-hash");
assert!(result.is_err());
match result {
Err(JacsError::TrustError(msg)) => {
assert!(msg.contains("nonexistent-hash"), "Error should contain the hash");
assert!(msg.contains("trust_agent_with_key"), "Error should suggest trust_agent_with_key");
}
_ => panic!("Expected TrustError error"),
}
}
#[test]
fn test_timestamp_empty_string_rejected() {
let result = validate_signature_timestamp("");
assert!(result.is_err(), "Empty timestamp should be rejected");
if let Err(JacsError::SignatureVerificationFailed { reason }) = result {
assert!(
reason.contains("Invalid") || reason.contains("format"),
"Error should mention invalid format: {}",
reason
);
}
}
#[test]
fn test_timestamp_whitespace_only_rejected() {
let whitespace_timestamps = [" ", "\t\t", "\n\n", " \t\n "];
for ts in whitespace_timestamps {
let result = validate_signature_timestamp(ts);
assert!(
result.is_err(),
"Whitespace-only timestamp '{}' should be rejected",
ts.escape_debug()
);
}
}
#[test]
fn test_timestamp_extremely_far_future_rejected() {
let far_future = "3000-01-01T00:00:00Z";
let result = validate_signature_timestamp(far_future);
assert!(
result.is_err(),
"Extremely far future timestamp should be rejected"
);
}
#[test]
fn test_timestamp_truly_invalid_formats_rejected() {
let invalid_timestamps = [
"2024/01/01T00:00:00Z", "Jan 01, 2024", "1704067200", "2024-W01", ];
for ts in invalid_timestamps {
let result = validate_signature_timestamp(ts);
assert!(
result.is_err(),
"Invalid timestamp format '{}' should be rejected",
ts
);
}
}
#[test]
fn test_timestamp_with_injection_attempt() {
let injection_attempts = [
"2024-01-01T00:00:00Z; DROP TABLE users;",
"2024-01-01T00:00:00Z<script>",
"2024-01-01T00:00:00Z\x00null",
"2024-01-01T00:00:00Z' OR '1'='1",
];
for ts in injection_attempts {
let result = validate_signature_timestamp(ts);
assert!(
result.is_err(),
"Timestamp with injection attempt '{}' should be rejected",
ts.escape_debug()
);
}
}
#[test]
fn test_timestamp_unix_epoch_valid() {
let epoch = "1970-01-01T00:00:00Z";
let result = validate_signature_timestamp(epoch);
assert!(
result.is_ok(),
"Unix epoch should be valid when expiration disabled: {:?}",
result
);
}
#[test]
fn test_timestamp_y2k38_boundary() {
let y2k38 = "2038-01-19T03:14:07Z";
let result = validate_signature_timestamp(y2k38);
let _ = result;
}
#[test]
#[serial]
fn test_trust_agent_rejects_invalid_json() {
let _temp = setup_test_trust_dir();
let invalid_json_cases = [
"", "not json at all", "{", "[}", "{'invalid': 'single quotes'}", "{\"incomplete\":", ];
for invalid_json in invalid_json_cases {
let result = trust_agent(invalid_json);
assert!(
result.is_err(),
"Invalid JSON '{}' should be rejected",
invalid_json.escape_debug()
);
}
}
#[test]
#[serial]
fn test_trust_agent_rejects_missing_jacs_id() {
let _temp = setup_test_trust_dir();
let agent_json = r#"{
"name": "Test Agent",
"jacsSignature": {
"signature": "dGVzdA==",
"publicKeyHash": "abc123",
"date": "2024-01-01T00:00:00Z",
"fields": ["name"]
}
}"#;
let result = trust_agent(agent_json);
assert!(result.is_err());
match result {
Err(JacsError::DocumentMalformed { field, .. }) => {
assert!(
field.contains("jacsId"),
"Error should mention jacsId: {}",
field
);
}
_ => panic!("Expected DocumentMalformed error for missing jacsId"),
}
}
#[test]
#[serial]
fn test_trust_agent_rejects_null_fields() {
let _temp = setup_test_trust_dir();
let agent_json = r#"{
"jacsId": null,
"name": "Test Agent",
"jacsSignature": {
"signature": "dGVzdA==",
"publicKeyHash": "abc123",
"date": "2024-01-01T00:00:00Z",
"fields": ["name"]
}
}"#;
let result = trust_agent(agent_json);
assert!(result.is_err(), "Null jacsId should be rejected");
}
#[test]
#[serial]
fn test_trust_agent_rejects_wrong_type_fields() {
let _temp = setup_test_trust_dir();
let agent_json = r#"{
"jacsId": 12345,
"name": "Test Agent",
"jacsSignature": {
"signature": "dGVzdA==",
"publicKeyHash": "abc123",
"date": "2024-01-01T00:00:00Z",
"fields": ["name"]
}
}"#;
let result = trust_agent(agent_json);
assert!(result.is_err(), "Non-string jacsId should be rejected");
}
#[test]
#[serial]
fn test_trust_agent_rejects_empty_signature() {
let _temp = setup_test_trust_dir();
let agent_json = r#"{
"jacsId": "test-agent-id",
"name": "Test Agent",
"jacsSignature": {
"signature": "",
"publicKeyHash": "abc123",
"date": "2024-01-01T00:00:00Z",
"signingAlgorithm": "ring-Ed25519",
"fields": ["name"]
}
}"#;
let result = trust_agent(agent_json);
assert!(result.is_err());
}
#[test]
#[serial]
fn test_trust_agent_rejects_malformed_base64_signature() {
let _temp = setup_test_trust_dir();
let agent_json = r#"{
"jacsId": "test-agent-id",
"name": "Test Agent",
"jacsSignature": {
"signature": "!!!not-valid-base64!!!",
"publicKeyHash": "abc123",
"date": "2024-01-01T00:00:00Z",
"signingAlgorithm": "ring-Ed25519",
"fields": ["name"]
}
}"#;
let result = trust_agent(agent_json);
assert!(result.is_err());
}
#[test]
#[serial]
fn test_untrust_nonexistent_agent() {
let _temp = setup_test_trust_dir();
let result = untrust_agent("nonexistent-agent-id");
assert!(result.is_err());
match result {
Err(JacsError::AgentNotTrusted { agent_id }) => {
assert_eq!(agent_id, "nonexistent-agent-id", "Error should contain agent ID");
}
_ => panic!("Expected AgentNotTrusted error"),
}
}
#[test]
#[serial]
fn test_get_trusted_agent_nonexistent() {
let _temp = setup_test_trust_dir();
let result = get_trusted_agent("nonexistent-agent-id");
assert!(result.is_err());
match result {
Err(JacsError::TrustError(msg)) => {
assert!(msg.contains("nonexistent-agent-id"), "Error should contain agent ID");
assert!(msg.contains("not in the trust store"), "Error should explain the issue");
assert!(msg.contains("trust_agent"), "Error should suggest using trust_agent");
}
_ => panic!("Expected TrustError error"),
}
}
#[test]
#[serial]
fn test_trust_agent_rejects_future_signature_timestamp() {
let _temp = setup_test_trust_dir();
let test_key = b"test-public-key";
let hash = "test-future-hash";
save_public_key_to_cache(hash, test_key, Some("ring-Ed25519")).unwrap();
let far_future = (now_utc() + chrono::Duration::hours(1)).to_rfc3339();
let agent_json = format!(r#"{{
"jacsId": "test-agent-id",
"name": "Test Agent",
"jacsSignature": {{
"signature": "dGVzdA==",
"publicKeyHash": "{}",
"date": "{}",
"signingAlgorithm": "ring-Ed25519",
"fields": ["name"]
}}
}}"#, hash, far_future);
let result = trust_agent(&agent_json);
assert!(result.is_err());
}
#[test]
fn test_algorithm_detection_with_empty_key() {
use crate::crypt::detect_algorithm_from_public_key;
let result = detect_algorithm_from_public_key(&[]);
assert!(result.is_err(), "Empty public key should fail detection");
}
#[test]
fn test_algorithm_detection_with_very_short_key() {
use crate::crypt::detect_algorithm_from_public_key;
let short_keys = [
vec![0u8; 1],
vec![0u8; 10],
vec![0u8; 20],
];
for key in short_keys {
let _ = detect_algorithm_from_public_key(&key);
}
}
}