use super::{sign, KeyPair, SeaError};
use serde_json::{json, Value};
use std::collections::HashMap;
#[derive(Clone, Debug)]
pub struct Certificate {
pub certificants: Certificants,
pub expiry: Option<f64>,
pub read_policy: Option<Policy>,
pub write_policy: Option<Policy>,
pub read_block: Option<String>,
pub write_block: Option<String>,
}
#[derive(Clone, Debug)]
pub enum Certificants {
Wildcard,
List(Vec<String>),
}
#[derive(Clone, Debug)]
pub enum Policy {
String(String),
Radix(RadixPolicy),
Array(Vec<Policy>),
}
#[derive(Clone, Debug)]
pub struct RadixPolicy {
pub patterns: HashMap<String, String>,
}
#[derive(Clone, Debug, Default)]
pub struct CertifyOptions {
pub expiry: Option<f64>,
pub block: Option<BlockPolicy>,
pub raw: bool,
}
#[derive(Clone, Debug)]
pub struct BlockPolicy {
pub read: Option<String>,
pub write: Option<String>,
}
pub async fn certify(
certificants: Certificants,
policy: Policy,
authority: &KeyPair,
opt: CertifyOptions,
) -> Result<String, SeaError> {
let certs = match certificants {
Certificants::Wildcard => "*".to_string(),
Certificants::List(list) => {
if list.len() == 1 {
list[0].clone()
} else {
serde_json::to_string(&list)
.map_err(|e| SeaError::Crypto(format!("Serialization error: {}", e)))?
}
}
};
let (read_policy, write_policy): (Option<String>, Option<String>) = match policy {
Policy::String(s) => (None, Some(s)),
Policy::Radix(r) => {
(
None,
Some(serde_json::to_string(&r.patterns).unwrap_or_default()),
)
}
Policy::Array(policies) => {
let policy_strs: Vec<String> = policies.iter().map(|p| format!("{:?}", p)).collect();
(
None,
Some(serde_json::to_string(&policy_strs).unwrap_or_default()),
)
}
};
let mut cert_data = json!({
"c": certs, });
if let Some(expiry) = opt.expiry {
cert_data["e"] = json!(expiry);
}
if let Some(ref read) = read_policy {
cert_data["r"] = json!(read);
}
if let Some(ref write) = write_policy {
cert_data["w"] = json!(write);
}
if let Some(ref block) = opt.block {
if let Some(ref read_block) = block.read {
cert_data["rb"] = json!(read_block);
}
if let Some(ref write_block) = block.write {
cert_data["wb"] = json!(write_block);
}
}
let signature = sign(&cert_data, authority).await?;
if opt.raw {
serde_json::to_string(&signature)
.map_err(|e| SeaError::Crypto(format!("Serialization error: {}", e)))
} else {
let sig_str = serde_json::to_string(&signature)
.map_err(|e| SeaError::Crypto(format!("Serialization error: {}", e)))?;
Ok(format!("SEA{}", sig_str))
}
}
pub async fn verify_certificate(cert: &str, authority_pub: &str) -> Result<Certificate, SeaError> {
let cert_data = if let Some(stripped) = cert.strip_prefix("SEA{") {
let mut json_str = String::from("{");
json_str.push_str(stripped);
json_str
} else if let Some(stripped) = cert.strip_prefix("SEA") {
stripped.to_string()
} else {
cert.to_string()
};
let signed_data: Value = serde_json::from_str(&cert_data)
.map_err(|e| SeaError::Crypto(format!("Parse error: {}", e)))?;
use super::verify;
let parsed = verify(&signed_data, authority_pub).await?;
let certificants = if let Some(c) = parsed.get("c") {
if let Some(s) = c.as_str() {
if s == "*" {
Certificants::Wildcard
} else {
Certificants::List(vec![s.to_string()])
}
} else if let Some(arr) = c.as_array() {
Certificants::List(
arr.iter()
.filter_map(|v| v.as_str().map(|s| s.to_string()))
.collect(),
)
} else {
return Err(SeaError::Crypto("Invalid certificants format".to_string()));
}
} else {
return Err(SeaError::Crypto("Missing certificants".to_string()));
};
let expiry = parsed.get("e").and_then(|v| v.as_f64());
let read_policy = parsed
.get("r")
.and_then(|v| v.as_str().map(|s| Policy::String(s.to_string())));
let write_policy = parsed
.get("w")
.and_then(|v| v.as_str().map(|s| Policy::String(s.to_string())));
let read_block = parsed
.get("rb")
.and_then(|v| v.as_str().map(|s| s.to_string()));
let write_block = parsed
.get("wb")
.and_then(|v| v.as_str().map(|s| s.to_string()));
Ok(Certificate {
certificants,
expiry,
read_policy,
write_policy,
read_block,
write_block,
})
}
pub fn matches_policy(path: &str, policy: &Policy) -> bool {
match policy {
Policy::String(s) => {
path == s || path.starts_with(&format!("{}/", s))
}
Policy::Radix(r) => {
for pattern in r.patterns.keys() {
if pattern == "*" {
return true; }
if path.starts_with(pattern) {
return true;
}
}
false
}
Policy::Array(policies) => {
policies.iter().any(|p| matches_policy(path, p))
}
}
}
pub fn check_permission(
cert: &Certificate,
path: &str,
operation: &str, ) -> bool {
if let Some(expiry) = cert.expiry {
let now = chrono::Utc::now().timestamp_millis() as f64;
if now > expiry {
return false; }
}
if operation == "read" {
if let Some(ref block) = cert.read_block {
if path.contains(block) {
return false; }
}
} else if operation == "write" {
if let Some(ref block) = cert.write_block {
if path.contains(block) {
return false; }
}
}
if operation == "read" {
if let Some(ref policy) = cert.read_policy {
return matches_policy(path, policy);
}
} else if operation == "write" {
if let Some(ref policy) = cert.write_policy {
return matches_policy(path, policy);
}
}
false
}