use super::CertificateRefusal;
use serde_json::Value;
pub fn request(holder: &str) -> Result<Vec<u8>, CertificateRefusal> {
if holder.is_empty()
|| !holder
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_'))
{
return Err(CertificateRefusal::HolderMismatch);
}
serde_json::to_vec(&serde_json::json!({"jsonrpc":"2.0", "id":1, "method":"tools/call",
"params":{"name":"read", "arguments":{"method":"GET", "path":format!("/agents/{holder}/certificates")}}}))
.map_err(|_| CertificateRefusal::StatusUnavailable)
}
pub fn verify_view(view: &Value, holder: &str, der: &[u8]) -> Result<(), CertificateRefusal> {
if der.is_empty() {
return Err(CertificateRefusal::Encoding);
}
if view["agent"].as_str() != Some(holder) {
return Err(CertificateRefusal::HolderMismatch);
}
if view["claims_are_live"] != false {
return Err(CertificateRefusal::StatusUnavailable);
}
let expected = standard_base64(der);
let mut matching = view["certificates"]
.as_array()
.ok_or(CertificateRefusal::StatusUnavailable)?
.iter()
.filter(|entry| entry["der"].as_str() == Some(expected.as_str()));
let certificate = matching
.next()
.ok_or(CertificateRefusal::StatusUnavailable)?;
if matching.next().is_some() {
return Err(CertificateRefusal::StatusUnavailable);
}
match certificate.get("withdrawn") {
Some(Value::Null) => Ok(()),
Some(Value::Object(_)) => Err(CertificateRefusal::Withdrawn),
_ => Err(CertificateRefusal::StatusUnavailable),
}
}
pub(super) fn response(bytes: &[u8], holder: &str, der: &[u8]) -> Result<(), CertificateRefusal> {
let rpc: Value =
serde_json::from_slice(bytes).map_err(|_| CertificateRefusal::StatusUnavailable)?;
if rpc["jsonrpc"] != "2.0"
|| rpc["id"] != 1
|| rpc.get("error").is_some()
|| rpc["result"]["isError"] != false
|| rpc["result"]["structuredContent"]["status"] != 200
{
return Err(CertificateRefusal::StatusUnavailable);
}
verify_view(&rpc["result"]["structuredContent"]["body"], holder, der)
}
fn standard_base64(bytes: &[u8]) -> String {
const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut output = String::new();
for chunk in bytes.chunks(3) {
let first = chunk[0];
let second = chunk.get(1).copied().unwrap_or(0);
let third = chunk.get(2).copied().unwrap_or(0);
output.push(char::from(ALPHABET[usize::from(first >> 2)]));
output.push(char::from(
ALPHABET[usize::from((first & 3) << 4 | second >> 4)],
));
output.push(if chunk.len() > 1 {
char::from(ALPHABET[usize::from((second & 15) << 2 | third >> 6)])
} else {
'='
});
output.push(if chunk.len() > 2 {
char::from(ALPHABET[usize::from(third & 63)])
} else {
'='
});
}
output
}