use crate::link_handshake::MachineIdentity;
use lys_core::ca::{
LYS_OID_ARC, certificate_signing_key, decode_extension, verify_certificate_chain_at,
};
use std::time::{Duration, UNIX_EPOCH};
#[path = "link_certificate/offer.rs"]
pub mod offer;
#[path = "link_certificate/service_key.rs"]
pub mod service_key;
#[path = "link_certificate/setup.rs"]
pub mod setup;
#[path = "link_certificate/standing.rs"]
pub mod standing;
#[path = "link_certificate/status.rs"]
pub mod status;
#[cfg(test)]
#[path = "link_certificate_tests.rs"]
mod tests;
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
pub enum CertificateRefusal {
#[error("certificate_too_large")]
Bound,
#[error("certificate_encoding_refused")]
Encoding,
#[error("certificate_signature_refused")]
Signature,
#[error("certificate_expired")]
Expired,
#[error("certificate_not_yet_valid")]
NotYetValid,
#[error("certificate_holder_mismatch")]
HolderMismatch,
#[error("certificate_key_mismatch")]
KeyMismatch,
#[error("certificate_claims_refused")]
Claims,
#[error("certificate_status_unavailable")]
StatusUnavailable,
#[error("certificate_withdrawn")]
Withdrawn,
#[error("certificate_service_key_refused")]
ServiceKey,
#[error("certificate_path_refused")]
Path,
#[error("certificate_proof_refused")]
Proof,
}
pub struct VerifiedCertificate {
machine: MachineIdentity,
digest: blake3::Hash,
not_after: u64,
}
impl std::fmt::Debug for VerifiedCertificate {
fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
out.debug_struct("VerifiedCertificate")
.field("digest", &self.digest)
.field("not_after", &self.not_after)
.finish_non_exhaustive()
}
}
impl VerifiedCertificate {
#[must_use]
pub const fn machine(&self) -> MachineIdentity {
self.machine
}
#[must_use]
pub const fn digest(&self) -> &blake3::Hash {
&self.digest
}
#[must_use]
pub const fn not_after(&self) -> u64 {
self.not_after
}
}
pub fn verify(
der: &[u8],
service_key: &[u8; 32],
holder: &str,
offered: MachineIdentity,
now: u64,
bound: usize,
) -> Result<VerifiedCertificate, CertificateRefusal> {
if bound == 0 || der.len() > bound {
return Err(CertificateRefusal::Bound);
}
exact_der(der)?;
let key = certificate_signing_key(der).map_err(|_| CertificateRefusal::Encoding)?;
if i128::from(now) < i128::from(key.not_before) {
return Err(CertificateRefusal::NotYetValid);
}
if i128::from(now) > i128::from(key.not_after) {
return Err(CertificateRefusal::Expired);
}
let at = UNIX_EPOCH
.checked_add(Duration::from_secs(now))
.ok_or(CertificateRefusal::Encoding)?;
verify_certificate_chain_at(der, service_key, at.into())
.map_err(|_| CertificateRefusal::Signature)?;
let mut oid = LYS_OID_ARC.to_vec();
oid.push(1);
let claims = decode_extension(der, &oid)
.map_err(|_| CertificateRefusal::Claims)?
.ok_or(CertificateRefusal::Claims)?;
let claims: serde_json::Value =
serde_json::from_slice(&claims).map_err(|_| CertificateRefusal::Claims)?;
if claims["agent"].as_str() != Some(holder) {
return Err(CertificateRefusal::HolderMismatch);
}
if key.public_key != *offered.ed25519_public() {
return Err(CertificateRefusal::KeyMismatch);
}
Ok(VerifiedCertificate {
machine: offered,
digest: blake3::hash(der),
not_after: u64::try_from(key.not_after).map_err(|_| CertificateRefusal::Encoding)?,
})
}
fn exact_der(der: &[u8]) -> Result<(), CertificateRefusal> {
let [0x30, first, rest @ ..] = der else {
return Err(CertificateRefusal::Encoding);
};
let (length, header) = if *first < 128 {
(usize::from(*first), 2)
} else {
let digits = usize::from(first & 0x7f);
if digits == 0 || digits > std::mem::size_of::<usize>() {
return Err(CertificateRefusal::Encoding);
}
let encoded = rest.get(..digits).ok_or(CertificateRefusal::Encoding)?;
if encoded[0] == 0 {
return Err(CertificateRefusal::Encoding);
}
let length = encoded
.iter()
.try_fold(0_usize, |length, byte| {
length.checked_mul(256)?.checked_add(usize::from(*byte))
})
.ok_or(CertificateRefusal::Encoding)?;
if length < 128 {
return Err(CertificateRefusal::Encoding);
}
(length, 2 + digits)
};
if length.checked_add(header) != Some(der.len()) {
return Err(CertificateRefusal::Encoding);
}
Ok(())
}