use super::CertificateRefusal;
use crate::link_handshake::NOISE_OVERHEAD;
use std::io::Read;
use std::path::Path;
use zeroize::Zeroizing;
const MAGIC: &[u8; 8] = b"LMCERT02";
pub(crate) fn for_machine(
host: &crate::link::LinkHost,
agent: &str,
first: &[u8],
) -> Result<Vec<u8>, CertificateRefusal> {
let Some(peers) = host.peers() else {
return Ok(first.to_vec());
};
let Some(path) = peers.certificate_path.as_deref() else {
return Ok(first.to_vec());
};
let standing = host
.standing()
.ok_or(CertificateRefusal::StatusUnavailable)?;
let endpoint = standing.endpoint(agent)?;
let pass_proof = host.local().certificate_proof(
agent,
endpoint.path(),
super::standing::Standing::body(),
)?;
let status_endpoint = standing.status_endpoint()?;
let status_proof = host.local().certificate_proof(
agent,
super::standing::Standing::status_signature_path(&status_endpoint),
&super::status::request(agent)?,
)?;
let proof = format!("{pass_proof}\n{status_proof}");
configured(
Some(path),
first,
&proof,
peers.max_certificate_bytes.get(),
peers.max_proof_bytes.get(),
)
}
pub fn configured(
path: Option<&Path>,
first: &[u8],
proof: &str,
bound: usize,
proof_bound: usize,
) -> Result<Vec<u8>, CertificateRefusal> {
let Some(path) = path else {
return Ok(first.to_vec());
};
let mut file = std::fs::File::open(path).map_err(|_| CertificateRefusal::Path)?;
if file.metadata().map_err(|_| CertificateRefusal::Path)?.len()
> u64::try_from(bound).map_err(|_| CertificateRefusal::Bound)?
{
return Err(CertificateRefusal::Bound);
}
let mut der = Zeroizing::new(Vec::new());
file.by_ref()
.take(u64::try_from(bound).map_err(|_| CertificateRefusal::Bound)? + 1)
.read_to_end(&mut der)
.map_err(|_| CertificateRefusal::Path)?;
pack(&der, first, proof, bound, proof_bound)
}
pub fn pack(
der: &[u8],
first: &[u8],
proof: &str,
bound: usize,
proof_bound: usize,
) -> Result<Vec<u8>, CertificateRefusal> {
if der.is_empty() || der.len() > bound || bound == 0 {
return Err(CertificateRefusal::Bound);
}
if first.is_empty() || first.len() > NOISE_OVERHEAD {
return Err(CertificateRefusal::Encoding);
}
if proof.is_empty() || proof.len() > proof_bound {
return Err(CertificateRefusal::Proof);
}
let length = u32::try_from(der.len()).map_err(|_| CertificateRefusal::Bound)?;
let mut bytes = MAGIC.to_vec();
bytes.extend_from_slice(&length.to_be_bytes());
bytes.extend_from_slice(
&u32::try_from(proof.len())
.map_err(|_| CertificateRefusal::Bound)?
.to_be_bytes(),
);
bytes.extend_from_slice(der);
bytes.extend_from_slice(proof.as_bytes());
bytes.extend_from_slice(first);
Ok(bytes)
}
pub struct OfferedCertificate<'a> {
pub der: &'a [u8],
pub proof: &'a str,
}
pub fn unpack(
bytes: &[u8],
bound: usize,
proof_bound: usize,
) -> Result<(Option<OfferedCertificate<'_>>, &[u8]), CertificateRefusal> {
if bytes.len() <= NOISE_OVERHEAD {
return Ok((None, bytes));
}
if !bytes.starts_with(MAGIC) {
return Err(CertificateRefusal::Encoding);
}
let encoded = bytes.get(8..12).ok_or(CertificateRefusal::Encoding)?;
let length = usize::try_from(u32::from_be_bytes(
encoded
.try_into()
.map_err(|_| CertificateRefusal::Encoding)?,
))
.map_err(|_| CertificateRefusal::Bound)?;
if length == 0 || length > bound {
return Err(CertificateRefusal::Bound);
}
let proof_length = usize::try_from(u32::from_be_bytes(
bytes
.get(12..16)
.ok_or(CertificateRefusal::Encoding)?
.try_into()
.map_err(|_| CertificateRefusal::Encoding)?,
))
.map_err(|_| CertificateRefusal::Bound)?;
if proof_length == 0 || proof_length > proof_bound {
return Err(CertificateRefusal::Proof);
}
let end = 16_usize
.checked_add(length)
.ok_or(CertificateRefusal::Bound)?;
let proof_end = end
.checked_add(proof_length)
.ok_or(CertificateRefusal::Bound)?;
let der = bytes.get(16..end).ok_or(CertificateRefusal::Encoding)?;
let proof = std::str::from_utf8(
bytes
.get(end..proof_end)
.ok_or(CertificateRefusal::Encoding)?,
)
.map_err(|_| CertificateRefusal::Proof)?;
let first = bytes.get(proof_end..).ok_or(CertificateRefusal::Encoding)?;
if first.is_empty() || first.len() > NOISE_OVERHEAD {
return Err(CertificateRefusal::Encoding);
}
Ok((Some(OfferedCertificate { der, proof }), first))
}