liminal_server/
link_certificate.rs1use crate::link_handshake::MachineIdentity;
4use lys_core::ca::{
5 LYS_OID_ARC, certificate_signing_key, decode_extension, verify_certificate_chain_at,
6};
7use std::time::{Duration, UNIX_EPOCH};
8
9#[path = "link_certificate/offer.rs"]
10pub mod offer;
11#[path = "link_certificate/service_key.rs"]
12pub mod service_key;
13#[path = "link_certificate/setup.rs"]
14pub mod setup;
15#[path = "link_certificate/standing.rs"]
16pub mod standing;
17#[path = "link_certificate/status.rs"]
18pub mod status;
19#[cfg(test)]
20#[path = "link_certificate_tests.rs"]
21mod tests;
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
25pub enum CertificateRefusal {
26 #[error("certificate_too_large")]
28 Bound,
29 #[error("certificate_encoding_refused")]
31 Encoding,
32 #[error("certificate_signature_refused")]
34 Signature,
35 #[error("certificate_expired")]
37 Expired,
38 #[error("certificate_not_yet_valid")]
40 NotYetValid,
41 #[error("certificate_holder_mismatch")]
43 HolderMismatch,
44 #[error("certificate_key_mismatch")]
46 KeyMismatch,
47 #[error("certificate_claims_refused")]
49 Claims,
50 #[error("certificate_status_unavailable")]
52 StatusUnavailable,
53 #[error("certificate_withdrawn")]
55 Withdrawn,
56 #[error("certificate_service_key_refused")]
58 ServiceKey,
59 #[error("certificate_path_refused")]
61 Path,
62 #[error("certificate_proof_refused")]
64 Proof,
65}
66
67pub struct VerifiedCertificate {
70 machine: MachineIdentity,
71 digest: blake3::Hash,
72 not_after: u64,
73}
74
75impl std::fmt::Debug for VerifiedCertificate {
76 fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 out.debug_struct("VerifiedCertificate")
78 .field("digest", &self.digest)
79 .field("not_after", &self.not_after)
80 .finish_non_exhaustive()
81 }
82}
83
84impl VerifiedCertificate {
85 #[must_use]
87 pub const fn machine(&self) -> MachineIdentity {
88 self.machine
89 }
90 #[must_use]
92 pub const fn digest(&self) -> &blake3::Hash {
93 &self.digest
94 }
95 #[must_use]
97 pub const fn not_after(&self) -> u64 {
98 self.not_after
99 }
100}
101
102pub fn verify(
108 der: &[u8],
109 service_key: &[u8; 32],
110 holder: &str,
111 offered: MachineIdentity,
112 now: u64,
113 bound: usize,
114) -> Result<VerifiedCertificate, CertificateRefusal> {
115 if bound == 0 || der.len() > bound {
116 return Err(CertificateRefusal::Bound);
117 }
118 exact_der(der)?;
119 let key = certificate_signing_key(der).map_err(|_| CertificateRefusal::Encoding)?;
120 if i128::from(now) < i128::from(key.not_before) {
121 return Err(CertificateRefusal::NotYetValid);
122 }
123 if i128::from(now) > i128::from(key.not_after) {
124 return Err(CertificateRefusal::Expired);
125 }
126 let at = UNIX_EPOCH
127 .checked_add(Duration::from_secs(now))
128 .ok_or(CertificateRefusal::Encoding)?;
129 verify_certificate_chain_at(der, service_key, at.into())
130 .map_err(|_| CertificateRefusal::Signature)?;
131 let mut oid = LYS_OID_ARC.to_vec();
132 oid.push(1);
133 let claims = decode_extension(der, &oid)
134 .map_err(|_| CertificateRefusal::Claims)?
135 .ok_or(CertificateRefusal::Claims)?;
136 let claims: serde_json::Value =
137 serde_json::from_slice(&claims).map_err(|_| CertificateRefusal::Claims)?;
138 if claims["agent"].as_str() != Some(holder) {
139 return Err(CertificateRefusal::HolderMismatch);
140 }
141 if key.public_key != *offered.ed25519_public() {
142 return Err(CertificateRefusal::KeyMismatch);
143 }
144 Ok(VerifiedCertificate {
145 machine: offered,
146 digest: blake3::hash(der),
147 not_after: u64::try_from(key.not_after).map_err(|_| CertificateRefusal::Encoding)?,
148 })
149}
150
151fn exact_der(der: &[u8]) -> Result<(), CertificateRefusal> {
152 let [0x30, first, rest @ ..] = der else {
153 return Err(CertificateRefusal::Encoding);
154 };
155 let (length, header) = if *first < 128 {
156 (usize::from(*first), 2)
157 } else {
158 let digits = usize::from(first & 0x7f);
159 if digits == 0 || digits > std::mem::size_of::<usize>() {
160 return Err(CertificateRefusal::Encoding);
161 }
162 let encoded = rest.get(..digits).ok_or(CertificateRefusal::Encoding)?;
163 if encoded[0] == 0 {
164 return Err(CertificateRefusal::Encoding);
165 }
166 let length = encoded
167 .iter()
168 .try_fold(0_usize, |length, byte| {
169 length.checked_mul(256)?.checked_add(usize::from(*byte))
170 })
171 .ok_or(CertificateRefusal::Encoding)?;
172 if length < 128 {
173 return Err(CertificateRefusal::Encoding);
174 }
175 (length, 2 + digits)
176 };
177 if length.checked_add(header) != Some(der.len()) {
178 return Err(CertificateRefusal::Encoding);
179 }
180 Ok(())
181}