1use std::collections::HashSet;
9use std::time::{SystemTime, UNIX_EPOCH};
10
11use x509_parser::certificate::X509Certificate;
12use x509_parser::extensions::{GeneralName, ParsedExtension};
13use x509_parser::pem::Pem;
14use x509_parser::prelude::FromDer;
15use x509_parser::time::ASN1Time;
16
17use crate::bridges::{Bridge, BridgeError, BridgeKind};
18use crate::generated::{
19 ActorIdentity, ActorIdentity_IdentityVersion, ActorType, AuthorityRoot, AuthorityRoot_Kind,
20 PublicKey, PublicKey_Purpose, TrustLevel,
21};
22
23pub fn default_eku_to_action(oid: &str) -> Option<&'static str> {
25 match oid {
26 "1.3.6.1.5.5.7.3.1" => Some("tls.server-auth"),
27 "1.3.6.1.5.5.7.3.2" => Some("tls.client-auth"),
28 "1.3.6.1.5.5.7.3.3" => Some("code.sign"),
29 "1.3.6.1.5.5.7.3.4" => Some("email.protect"),
30 "1.3.6.1.5.5.7.3.8" => Some("timestamp.sign"),
31 "1.3.6.1.5.5.7.3.9" => Some("ocsp.sign"),
32 _ => None,
33 }
34}
35
36#[derive(Clone, Debug)]
37pub struct TlsBridgeConfig {
38 pub bridge_id: String,
39 pub trust_domain: String,
40 pub root_certificates_pem: Vec<String>,
41 pub max_chain_length: Option<usize>,
42 pub required_san_uri: Option<String>,
43 pub now_unix_seconds: Option<u64>,
44}
45
46#[derive(Clone, Debug)]
47pub struct TlsVerificationResult {
48 pub identity: ActorIdentity,
49 pub capabilities: Vec<String>,
50 pub leaf_subject: String,
51 pub chain_subjects: Vec<String>,
52}
53
54pub struct TlsBridge {
55 cfg: TlsBridgeConfig,
56 roots: Vec<Vec<u8>>, }
58
59impl TlsBridge {
60 pub fn new(cfg: TlsBridgeConfig) -> Result<Self, BridgeError> {
61 if cfg.root_certificates_pem.is_empty() {
62 return Err(BridgeError::InvalidInput(
63 "TLS bridge requires at least one trust anchor".into(),
64 ));
65 }
66 let mut roots = Vec::with_capacity(cfg.root_certificates_pem.len());
67 for (i, pem) in cfg.root_certificates_pem.iter().enumerate() {
68 let der = parse_single_pem(pem)
69 .map_err(|e| BridgeError::InvalidInput(format!("root[{}]: {}", i, e)))?;
70 roots.push(der);
71 }
72 Ok(TlsBridge { cfg, roots })
73 }
74
75 pub fn verify_chain(&self, chain_pem: &[String]) -> Result<TlsVerificationResult, BridgeError> {
76 let mut chain_der: Vec<Vec<u8>> = Vec::new();
77 for (i, pem) in chain_pem.iter().enumerate() {
78 for der in parse_pem_bundle(pem)
79 .map_err(|e| BridgeError::InvalidInput(format!("chain[{}]: {}", i, e)))?
80 {
81 chain_der.push(der);
82 }
83 }
84 if chain_der.is_empty() {
85 return Err(BridgeError::InvalidInput("empty chain".into()));
86 }
87 let max = self.cfg.max_chain_length.unwrap_or(6);
88 if chain_der.len() > max {
89 return Err(BridgeError::Rejected(format!(
90 "chain longer than max ({} > {})",
91 chain_der.len(),
92 max
93 )));
94 }
95
96 let now = self.cfg.now_unix_seconds.unwrap_or_else(|| {
97 SystemTime::now()
98 .duration_since(UNIX_EPOCH)
99 .unwrap()
100 .as_secs()
101 });
102 let now_asn1 = ASN1Time::from_timestamp(now as i64)
103 .map_err(|e| BridgeError::InvalidInput(format!("now overflow: {}", e)))?;
104
105 let parsed: Vec<X509Certificate> = chain_der
107 .iter()
108 .map(|d| {
109 let (_, c) = X509Certificate::from_der(d)
110 .map_err(|e| BridgeError::InvalidInput(format!("DER parse: {}", e)))?;
111 Ok::<_, BridgeError>(c)
112 })
113 .collect::<Result<_, _>>()?;
114
115 for c in &parsed {
117 let validity = c.validity();
118 if !validity.is_valid_at(now_asn1) {
119 return Err(BridgeError::Rejected(format!(
120 "cert {} outside validity window",
121 c.subject()
122 )));
123 }
124 }
125
126 let leaf = &parsed[0];
128 let mut chain_subjects: Vec<String> = vec![leaf.subject().to_string()];
129 let mut current_idx: Option<usize> = Some(0);
130 let mut visited: HashSet<String> = HashSet::new();
131 visited.insert(leaf.subject().to_string());
132
133 let roots_parsed: Vec<X509Certificate> = self
134 .roots
135 .iter()
136 .map(|d| X509Certificate::from_der(d).map(|p| p.1))
137 .collect::<Result<_, _>>()
138 .map_err(|e| BridgeError::Internal(format!("root DER reparse: {}", e)))?;
139
140 for _ in 0..max {
141 let cur = &parsed[current_idx.expect("current set")];
142 let inter_idx = parsed
144 .iter()
145 .enumerate()
146 .find(|(i, c)| *i != current_idx.unwrap() && c.subject() == cur.issuer())
147 .map(|(i, _)| i);
148 let issuer_in_chain = inter_idx.map(|i| &parsed[i]);
149 let root_match = roots_parsed.iter().find(|r| r.subject() == cur.issuer());
150
151 let issuer = match issuer_in_chain.or(root_match) {
152 Some(c) => c,
153 None => {
154 return Err(BridgeError::Rejected(format!(
155 "no issuer cert for {} (issuer={})",
156 cur.subject(),
157 cur.issuer()
158 )))
159 }
160 };
161
162 cur.verify_signature(Some(issuer.public_key()))
163 .map_err(|e| {
164 BridgeError::Rejected(format!(
165 "signature verification failed for {}: {}",
166 cur.subject(),
167 e
168 ))
169 })?;
170
171 chain_subjects.push(issuer.subject().to_string());
172 if root_match.is_some() && issuer_in_chain.is_none() {
173 issuer
176 .verify_signature(Some(issuer.public_key()))
177 .map_err(|e| {
178 BridgeError::Rejected(format!(
179 "root {} not self-consistent: {}",
180 issuer.subject(),
181 e
182 ))
183 })?;
184 return self.project(leaf, issuer, chain_subjects);
185 }
186 current_idx = inter_idx;
187 if !visited.insert(issuer.subject().to_string()) {
188 return Err(BridgeError::Rejected("chain loop detected".into()));
189 }
190 }
191 Err(BridgeError::Rejected(format!(
192 "chain exceeds max depth {} without reaching trust anchor",
193 max
194 )))
195 }
196
197 fn project(
198 &self,
199 leaf: &X509Certificate,
200 root: &X509Certificate,
201 chain_subjects: Vec<String>,
202 ) -> Result<TlsVerificationResult, BridgeError> {
203 let san_uris = collect_san_uris(leaf);
204 if let Some(req) = &self.cfg.required_san_uri {
205 if !san_uris.iter().any(|u| u == req) {
206 return Err(BridgeError::Rejected(format!(
207 "leaf SAN URIs {:?} missing required {}",
208 san_uris, req
209 )));
210 }
211 }
212 let cn = parse_common_name(&leaf.subject().to_string());
213 let san_dns = collect_san_dns(leaf);
214 let spiffe_san = san_uris
215 .iter()
216 .find(|u| u.starts_with("spiffe://"))
217 .cloned();
218 let subject = spiffe_san
219 .clone()
220 .or(cn.clone())
221 .or_else(|| san_dns.first().cloned())
222 .unwrap_or_else(|| leaf.subject().to_string());
223 let actor_type = if spiffe_san.is_some() {
224 ActorType::Service
225 } else {
226 ActorType::Device
227 };
228 let type_str = match actor_type {
229 ActorType::Service => "service",
230 _ => "device",
231 };
232 let actor_id = format!(
233 "tf:actor:{}:{}/{}",
234 type_str,
235 self.cfg.trust_domain,
236 encode_actor_path(&subject)
237 );
238
239 let pk = leaf.public_key();
240 let alg_oid = pk.algorithm.algorithm.to_id_string();
241 let algorithm = match alg_oid.as_str() {
242 "1.2.840.113549.1.1.1" => "rsa",
243 "1.2.840.10045.2.1" => "p256",
244 "1.3.101.112" => "ed25519",
245 _ => "unknown",
246 };
247 let public_key_b64 =
248 crate::encoding::STANDARD.encode(pk.subject_public_key.data.as_ref());
249
250 let fingerprint = sha256_hex(leaf.as_ref());
251
252 let identity = ActorIdentity {
253 identity_version: ActorIdentity_IdentityVersion::V1,
254 actor_id,
255 actor_type: actor_type.clone(),
256 instance_id: None,
257 public_keys: vec![PublicKey {
258 key_id: fingerprint,
259 algorithm: algorithm.to_string(),
260 public_key: public_key_b64,
261 purpose: PublicKey_Purpose::Signing,
262 valid_from: None,
263 valid_until: None,
264 }],
265 trust_levels: vec![if matches!(actor_type, ActorType::Service) {
266 TrustLevel::T4
267 } else {
268 TrustLevel::T3
269 }],
270 authority_roots: vec![AuthorityRoot {
271 kind: AuthorityRoot_Kind::Organization,
272 id: parse_common_name(&root.subject().to_string())
273 .unwrap_or_else(|| root.subject().to_string()),
274 }],
275 attestations: None,
276 valid_from: rfc3339_from_unix(leaf.validity().not_before.timestamp()),
277 valid_until: Some(rfc3339_from_unix(leaf.validity().not_after.timestamp())),
278 revocation_ref: None,
279 signature: None,
280 };
281
282 let capabilities = collect_eku_actions(leaf);
283
284 Ok(TlsVerificationResult {
285 identity,
286 capabilities,
287 leaf_subject: leaf.subject().to_string(),
288 chain_subjects,
289 })
290 }
291}
292
293impl Bridge for TlsBridge {
294 fn bridge_id(&self) -> &str {
295 &self.cfg.bridge_id
296 }
297 fn kind(&self) -> BridgeKind {
298 BridgeKind::Tls
299 }
300 fn trust_domain(&self) -> &str {
301 &self.cfg.trust_domain
302 }
303}
304
305fn parse_single_pem(pem: &str) -> Result<Vec<u8>, String> {
306 let mut all = parse_pem_bundle(pem)?;
307 if all.is_empty() {
308 return Err("no CERTIFICATE block".into());
309 }
310 Ok(all.remove(0))
311}
312
313fn parse_pem_bundle(pem: &str) -> Result<Vec<Vec<u8>>, String> {
314 let mut bytes = pem.as_bytes();
315 let mut out = Vec::new();
316 while !bytes.is_empty() {
317 match Pem::read(std::io::Cursor::new(bytes)) {
318 Ok((p, consumed)) => {
319 if p.label != "CERTIFICATE" {
320 bytes = &bytes[consumed..];
321 continue;
322 }
323 out.push(p.contents);
324 bytes = &bytes[consumed..];
325 }
326 Err(_) => break,
327 }
328 }
329 Ok(out)
330}
331
332fn collect_san_uris(cert: &X509Certificate) -> Vec<String> {
333 cert.extensions()
334 .iter()
335 .flat_map(|ext| match ext.parsed_extension() {
336 ParsedExtension::SubjectAlternativeName(san) => san
337 .general_names
338 .iter()
339 .filter_map(|gn| match gn {
340 GeneralName::URI(u) => Some(u.to_string()),
341 _ => None,
342 })
343 .collect::<Vec<_>>(),
344 _ => Vec::new(),
345 })
346 .collect()
347}
348
349fn collect_san_dns(cert: &X509Certificate) -> Vec<String> {
350 cert.extensions()
351 .iter()
352 .flat_map(|ext| match ext.parsed_extension() {
353 ParsedExtension::SubjectAlternativeName(san) => san
354 .general_names
355 .iter()
356 .filter_map(|gn| match gn {
357 GeneralName::DNSName(d) => Some(d.to_string()),
358 _ => None,
359 })
360 .collect::<Vec<_>>(),
361 _ => Vec::new(),
362 })
363 .collect()
364}
365
366fn collect_eku_actions(cert: &X509Certificate) -> Vec<String> {
367 let mut out = Vec::new();
368 for ext in cert.extensions() {
369 if let ParsedExtension::ExtendedKeyUsage(eku) = ext.parsed_extension() {
370 if eku.any {
371 continue;
372 }
373 for oid in &eku.other {
374 if let Some(action) = default_eku_to_action(&oid.to_id_string()) {
375 out.push(action.to_string());
376 }
377 }
378 if eku.client_auth {
379 out.push("tls.client-auth".to_string());
380 }
381 if eku.server_auth {
382 out.push("tls.server-auth".to_string());
383 }
384 if eku.code_signing {
385 out.push("code.sign".to_string());
386 }
387 if eku.email_protection {
388 out.push("email.protect".to_string());
389 }
390 if eku.time_stamping {
391 out.push("timestamp.sign".to_string());
392 }
393 if eku.ocsp_signing {
394 out.push("ocsp.sign".to_string());
395 }
396 }
397 }
398 let mut seen = HashSet::new();
400 out.into_iter().filter(|s| seen.insert(s.clone())).collect()
401}
402
403fn parse_common_name(distinguished_name: &str) -> Option<String> {
404 for part in distinguished_name.split(['\n', ',']) {
405 let trimmed = part.trim();
406 if let Some(rest) = trimmed
407 .strip_prefix("CN=")
408 .or_else(|| trimmed.strip_prefix("cn="))
409 {
410 return Some(rest.to_string());
411 }
412 }
413 None
414}
415
416fn encode_actor_path(s: &str) -> String {
417 let mut out = String::with_capacity(s.len());
418 for b in s.bytes() {
419 match b {
420 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' | b'/' => {
421 out.push(b as char);
422 }
423 _ => out.push_str(&format!("%{:02X}", b)),
424 }
425 }
426 out
427}
428
429fn sha256_hex(bytes: &[u8]) -> String {
430 use sha2::{Digest, Sha256};
431 let digest = Sha256::digest(bytes);
432 digest.iter().map(|b| format!("{:02x}", b)).collect()
433}
434
435fn rfc3339_from_unix(secs: i64) -> String {
436 let (y, m, d, h, mi, s) = secs_to_ymdhms(secs);
437 format!("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z", y, m, d, h, mi, s)
438}
439
440fn secs_to_ymdhms(secs: i64) -> (i32, u32, u32, u32, u32, u32) {
441 let days = secs.div_euclid(86_400);
442 let time = secs.rem_euclid(86_400);
443 let hour = (time / 3600) as u32;
444 let minute = ((time % 3600) / 60) as u32;
445 let second = (time % 60) as u32;
446 let z = days + 719_468;
447 let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
448 let doe = (z - era * 146_097) as u64;
449 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
450 let y = yoe as i64 + era * 400;
451 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
452 let mp = (5 * doy + 2) / 153;
453 let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
454 let m = if mp < 10 {
455 (mp + 3) as u32
456 } else {
457 (mp - 9) as u32
458 };
459 let year = if m <= 2 { y + 1 } else { y };
460 (year as i32, m, d, hour, minute, second)
461}
462
463
464#[derive(Clone, Debug)]
482pub struct X509Cert {
483 pub der: Vec<u8>,
484 pub subject: String,
485 pub serial_be: Vec<u8>,
486}
487
488impl X509Cert {
489 pub fn from_der(der: &[u8]) -> Result<Self, BridgeError> {
491 let (_, parsed) = X509Certificate::from_der(der)
492 .map_err(|e| BridgeError::InvalidInput(format!("X509Cert: {}", e)))?;
493 let serial_be = parsed.tbs_certificate.raw_serial().to_vec();
494 Ok(X509Cert {
495 der: der.to_vec(),
496 subject: parsed.subject().to_string(),
497 serial_be,
498 })
499 }
500
501 pub fn from_pem(pem: &str) -> Result<Self, BridgeError> {
503 let der = parse_single_pem(pem)
504 .map_err(|e| BridgeError::InvalidInput(format!("X509Cert PEM: {}", e)))?;
505 Self::from_der(&der)
506 }
507}
508
509#[derive(Clone, Debug, PartialEq, Eq)]
513pub enum OcspStatus {
514 Good,
515 Revoked,
516 Unknown,
517}
518
519pub trait OcspFetcher {
523 fn fetch(
524 &self,
525 cert: &X509Cert,
526 issuer: &X509Cert,
527 ocsp_url: &str,
528 ) -> Result<Vec<u8>, BridgeError>;
529}
530
531#[derive(Debug, thiserror::Error, PartialEq, Eq)]
533pub enum OcspError {
534 #[error("OCSP DER parse failed: {0}")]
535 Parse(String),
536 #[error("OCSP responder returned status code {0}")]
537 ResponderError(u8),
538 #[error("OCSP response is not a BasicOCSPResponse")]
539 NotBasic,
540 #[error("OCSP response thisUpdate={this_update} > now={now}")]
541 NotYetValid { this_update: i64, now: i64 },
542 #[error("OCSP response nextUpdate={next_update} < now={now}")]
543 Stale { next_update: i64, now: i64 },
544 #[error("OCSP response contained no SingleResponse entries")]
545 NoSingleResponses,
546}
547
548pub struct OcspCheck;
551
552impl OcspCheck {
553 pub fn query(
558 cert: &X509Cert,
559 issuer: &X509Cert,
560 fetcher: &dyn OcspFetcher,
561 ocsp_url: &str,
562 now_unix_seconds: i64,
563 ) -> Result<OcspStatus, BridgeError> {
564 let der = fetcher.fetch(cert, issuer, ocsp_url)?;
565 Self::parse_response(&der, now_unix_seconds).map_err(|e| match e {
566 OcspError::Parse(s) => BridgeError::InvalidInput(format!("OCSP: {}", s)),
567 OcspError::ResponderError(n) => {
568 BridgeError::Rejected(format!("OCSP responder error {}", n))
569 }
570 OcspError::NotBasic => BridgeError::Rejected("OCSP not BasicOCSPResponse".into()),
571 OcspError::NotYetValid { this_update, now } => {
572 BridgeError::Rejected(format!("OCSP thisUpdate={} > now={}", this_update, now))
573 }
574 OcspError::Stale { next_update, now } => {
575 BridgeError::Rejected(format!("OCSP nextUpdate={} < now={}", next_update, now))
576 }
577 OcspError::NoSingleResponses => {
578 BridgeError::Rejected("OCSP had no SingleResponse entries".into())
579 }
580 })
581 }
582
583 pub fn parse_response(der: &[u8], now_unix_seconds: i64) -> Result<OcspStatus, OcspError> {
587 let parsed = ocsp::parse_ocsp_response(der)?;
588 if parsed.response_status != 0 {
589 return Err(OcspError::ResponderError(parsed.response_status));
590 }
591 let basic = parsed.basic.ok_or(OcspError::NotBasic)?;
592 let single = basic
593 .single_responses
594 .first()
595 .ok_or(OcspError::NoSingleResponses)?;
596 if single.this_update > now_unix_seconds {
597 return Err(OcspError::NotYetValid {
598 this_update: single.this_update,
599 now: now_unix_seconds,
600 });
601 }
602 if let Some(next) = single.next_update {
603 if next < now_unix_seconds {
604 return Err(OcspError::Stale {
605 next_update: next,
606 now: now_unix_seconds,
607 });
608 }
609 }
610 Ok(single.status.clone())
611 }
612}
613
614pub mod ocsp {
622 use super::OcspError;
623 use super::OcspStatus;
624
625 #[derive(Clone, Debug)]
626 pub struct SingleResponse {
627 pub status: OcspStatus,
628 pub this_update: i64,
629 pub next_update: Option<i64>,
630 }
631
632 #[derive(Clone, Debug)]
633 pub struct BasicResponseData {
634 pub single_responses: Vec<SingleResponse>,
635 }
636
637 #[derive(Clone, Debug)]
638 pub struct OcspResponse {
639 pub response_status: u8,
640 pub basic: Option<BasicResponseData>,
641 }
642
643 const ID_PKIX_OCSP_BASIC: &[u8] = &[0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01];
645
646 pub fn parse_ocsp_response(der: &[u8]) -> Result<OcspResponse, OcspError> {
647 let outer = read_seq(der, 0).ok_or_else(|| OcspError::Parse("outer SEQUENCE".into()))?;
648 let mut p = outer.content_start;
649 let end = outer.content_start + outer.content_len;
650
651 let status_tlv =
653 read_tlv(der, p).ok_or_else(|| OcspError::Parse("responseStatus tag".into()))?;
654 if status_tlv.tag != 0x0a {
655 return Err(OcspError::Parse(format!(
656 "responseStatus expected ENUMERATED (0x0a), got 0x{:02x}",
657 status_tlv.tag
658 )));
659 }
660 if status_tlv.content_len != 1 {
661 return Err(OcspError::Parse("responseStatus len != 1".into()));
662 }
663 let response_status = der[status_tlv.content_start];
664 p = status_tlv.content_start + status_tlv.content_len;
665
666 let mut basic: Option<BasicResponseData> = None;
667 if p < end {
668 let rb_tlv = read_tlv(der, p).ok_or_else(|| OcspError::Parse("[0] tag".into()))?;
670 if rb_tlv.tag == 0xa0 {
671 let rb_seq = read_seq(der, rb_tlv.content_start)
672 .ok_or_else(|| OcspError::Parse("ResponseBytes SEQUENCE".into()))?;
673 let mut q = rb_seq.content_start;
674 let oid_tlv =
675 read_tlv(der, q).ok_or_else(|| OcspError::Parse("responseType OID".into()))?;
676 if oid_tlv.tag != 0x06 {
677 return Err(OcspError::Parse("responseType not OID".into()));
678 }
679 let oid_bytes =
680 &der[oid_tlv.content_start..oid_tlv.content_start + oid_tlv.content_len];
681 if oid_bytes != ID_PKIX_OCSP_BASIC {
682 } else {
684 q = oid_tlv.content_start + oid_tlv.content_len;
685 let response_octets = read_tlv(der, q)
686 .ok_or_else(|| OcspError::Parse("response OCTET STRING".into()))?;
687 if response_octets.tag != 0x04 {
688 return Err(OcspError::Parse("response not OCTET STRING".into()));
689 }
690 let basic_der = &der[response_octets.content_start
691 ..response_octets.content_start + response_octets.content_len];
692 basic = Some(parse_basic_response(basic_der)?);
693 }
694 }
695 }
696
697 Ok(OcspResponse {
698 response_status,
699 basic,
700 })
701 }
702
703 fn parse_basic_response(der: &[u8]) -> Result<BasicResponseData, OcspError> {
704 let basic_seq =
705 read_seq(der, 0).ok_or_else(|| OcspError::Parse("BasicOCSPResponse SEQ".into()))?;
706 let tbs = read_seq(der, basic_seq.content_start)
707 .ok_or_else(|| OcspError::Parse("tbsResponseData SEQ".into()))?;
708 let mut p = tbs.content_start;
711 let end = tbs.content_start + tbs.content_len;
712 if p < end && der[p] == 0xa0 {
714 let v = read_tlv(der, p).ok_or_else(|| OcspError::Parse("version".into()))?;
715 p = v.content_start + v.content_len;
716 }
717 if p < end {
719 let r = read_tlv(der, p).ok_or_else(|| OcspError::Parse("responderID".into()))?;
720 p = r.content_start + r.content_len;
721 }
722 if p < end {
724 let pa = read_tlv(der, p).ok_or_else(|| OcspError::Parse("producedAt".into()))?;
725 p = pa.content_start + pa.content_len;
726 }
727 let resp_seq =
729 read_seq(der, p).ok_or_else(|| OcspError::Parse("responses SEQUENCE OF".into()))?;
730 let mut single_responses: Vec<SingleResponse> = Vec::new();
731 let mut q = resp_seq.content_start;
732 let qend = resp_seq.content_start + resp_seq.content_len;
733 while q < qend {
734 let sr = read_seq(der, q).ok_or_else(|| OcspError::Parse("SingleResponse".into()))?;
735 single_responses.push(parse_single_response(
736 &der[sr.content_start..sr.content_start + sr.content_len],
737 )?);
738 q = sr.content_start + sr.content_len;
739 }
740 Ok(BasicResponseData { single_responses })
741 }
742
743 fn parse_single_response(der: &[u8]) -> Result<SingleResponse, OcspError> {
744 let mut p = 0usize;
747 let end = der.len();
748 let cert_id = read_seq(der, p).ok_or_else(|| OcspError::Parse("certID".into()))?;
750 p = cert_id.content_start + cert_id.content_len;
751 let cs = read_tlv(der, p).ok_or_else(|| OcspError::Parse("certStatus".into()))?;
753 let status = match cs.tag {
754 0x80 => OcspStatus::Good, 0xa1 => OcspStatus::Revoked, 0x82 => OcspStatus::Unknown, other => {
758 return Err(OcspError::Parse(format!(
759 "unknown certStatus tag 0x{:02x}",
760 other
761 )))
762 }
763 };
764 p = cs.content_start + cs.content_len;
765 let tu = read_tlv(der, p).ok_or_else(|| OcspError::Parse("thisUpdate".into()))?;
767 if tu.tag != 0x18 {
768 return Err(OcspError::Parse(format!(
769 "thisUpdate expected 0x18, got 0x{:02x}",
770 tu.tag
771 )));
772 }
773 let this_update =
774 parse_generalized_time(&der[tu.content_start..tu.content_start + tu.content_len])?;
775 p = tu.content_start + tu.content_len;
776 let mut next_update: Option<i64> = None;
778 if p < end && der[p] == 0xa0 {
779 let nu_outer =
780 read_tlv(der, p).ok_or_else(|| OcspError::Parse("nextUpdate [0]".into()))?;
781 let inner = read_tlv(der, nu_outer.content_start)
782 .ok_or_else(|| OcspError::Parse("nextUpdate inner".into()))?;
783 if inner.tag != 0x18 {
784 return Err(OcspError::Parse(format!(
785 "nextUpdate expected 0x18, got 0x{:02x}",
786 inner.tag
787 )));
788 }
789 next_update = Some(parse_generalized_time(
790 &der[inner.content_start..inner.content_start + inner.content_len],
791 )?);
792 }
793 Ok(SingleResponse {
794 status,
795 this_update,
796 next_update,
797 })
798 }
799
800 fn parse_generalized_time(bytes: &[u8]) -> Result<i64, OcspError> {
803 let s = std::str::from_utf8(bytes)
804 .map_err(|_| OcspError::Parse("generalized time non-utf8".into()))?;
805 if !s.ends_with('Z') {
806 return Err(OcspError::Parse(
807 "generalized time must be Zulu-suffixed".into(),
808 ));
809 }
810 let core = &s[..s.len() - 1];
811 if core.len() < 14 {
812 return Err(OcspError::Parse("generalized time too short".into()));
813 }
814 let y: i32 = core[0..4]
815 .parse()
816 .map_err(|_| OcspError::Parse("year".into()))?;
817 let m: u32 = core[4..6]
818 .parse()
819 .map_err(|_| OcspError::Parse("month".into()))?;
820 let d: u32 = core[6..8]
821 .parse()
822 .map_err(|_| OcspError::Parse("day".into()))?;
823 let hh: u32 = core[8..10]
824 .parse()
825 .map_err(|_| OcspError::Parse("hour".into()))?;
826 let mm: u32 = core[10..12]
827 .parse()
828 .map_err(|_| OcspError::Parse("min".into()))?;
829 let ss: u32 = core[12..14]
830 .parse()
831 .map_err(|_| OcspError::Parse("sec".into()))?;
832 Ok(ymdhms_to_unix(y, m, d, hh, mm, ss))
833 }
834
835 fn ymdhms_to_unix(y: i32, m: u32, d: u32, hh: u32, mm: u32, ss: u32) -> i64 {
836 let yy = if m <= 2 { y - 1 } else { y } as i64;
839 let era = if yy >= 0 { yy } else { yy - 399 } / 400;
840 let yoe = (yy - era * 400) as u64;
841 let mp = if m > 2 { m - 3 } else { m + 9 } as u64;
842 let doy = (153 * mp + 2) / 5 + (d as u64) - 1;
843 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
844 let days = era * 146_097 + doe as i64 - 719_468;
845 days * 86_400 + hh as i64 * 3600 + mm as i64 * 60 + ss as i64
846 }
847
848 pub(crate) struct Tlv {
851 pub tag: u8,
852 pub content_start: usize,
853 pub content_len: usize,
854 }
855
856 pub(crate) fn read_seq(buf: &[u8], pos: usize) -> Option<Tlv> {
857 let t = read_tlv(buf, pos)?;
858 if t.tag != 0x30 {
859 return None;
860 }
861 Some(t)
862 }
863
864 pub(crate) fn read_tlv(buf: &[u8], pos: usize) -> Option<Tlv> {
865 if pos >= buf.len() {
866 return None;
867 }
868 let tag = buf[pos];
869 let (len, header) = read_length(buf, pos + 1)?;
870 if pos + 1 + header + len > buf.len() {
871 return None;
872 }
873 Some(Tlv {
874 tag,
875 content_start: pos + 1 + header,
876 content_len: len,
877 })
878 }
879
880 fn read_length(buf: &[u8], pos: usize) -> Option<(usize, usize)> {
881 if pos >= buf.len() {
882 return None;
883 }
884 let b = buf[pos];
885 if b < 0x80 {
886 return Some((b as usize, 1));
887 }
888 let n = (b & 0x7f) as usize;
889 if n == 0 || n > 4 || pos + n >= buf.len() {
890 return None;
891 }
892 let mut len: usize = 0;
893 for i in 0..n {
894 len = (len << 8) | buf[pos + 1 + i] as usize;
895 }
896 Some((len, 1 + n))
897 }
898}
899
900#[derive(Clone, Debug, PartialEq, Eq)]
903pub struct RevocationEntry {
904 pub serial_be: Vec<u8>,
906 pub revocation_date: i64,
908 pub reason_code: Option<u8>,
910}
911
912#[derive(Debug, thiserror::Error, PartialEq, Eq)]
913pub enum CrlError {
914 #[error("CRL DER parse failed: {0}")]
915 Parse(String),
916}
917
918pub struct CrlIndex {
923 pub issuer: String,
924 pub this_update: i64,
925 pub next_update: Option<i64>,
926 revoked: std::collections::BTreeMap<Vec<u8>, RevocationEntry>,
927}
928
929impl CrlIndex {
930 pub fn issuer(&self) -> &str {
931 &self.issuer
932 }
933 pub fn len(&self) -> usize {
934 self.revoked.len()
935 }
936 pub fn is_empty(&self) -> bool {
937 self.revoked.is_empty()
938 }
939 pub fn is_revoked(&self, serial_be: &[u8]) -> Option<&RevocationEntry> {
943 let key = normalise_serial(serial_be);
944 self.revoked.get(&key)
945 }
946 pub fn entries(&self) -> impl Iterator<Item = &RevocationEntry> {
948 self.revoked.values()
949 }
950}
951
952fn normalise_serial(serial_be: &[u8]) -> Vec<u8> {
953 let mut s = serial_be;
954 while s.len() > 1 && s[0] == 0x00 {
955 s = &s[1..];
956 }
957 s.to_vec()
958}
959
960pub struct CrlCheck;
961
962impl CrlCheck {
963 pub fn load(crl_bytes: &[u8]) -> Result<CrlIndex, CrlError> {
964 use x509_parser::revocation_list::CertificateRevocationList;
965 let (_, crl) = CertificateRevocationList::from_der(crl_bytes)
966 .map_err(|e| CrlError::Parse(format!("{}", e)))?;
967 let issuer = crl.issuer().to_string();
968 let this_update = crl.last_update().timestamp();
969 let next_update = crl.next_update().map(|t| t.timestamp());
970 let mut revoked = std::collections::BTreeMap::new();
971 for r in crl.iter_revoked_certificates() {
972 let serial_be = normalise_serial(r.raw_serial());
973 let revocation_date = r.revocation_date.timestamp();
974 let reason_code = r
975 .extensions()
976 .iter()
977 .find_map(|ext| match ext.parsed_extension() {
978 x509_parser::extensions::ParsedExtension::ReasonCode(rc) => Some(rc.0),
979 _ => None,
980 });
981 revoked.insert(
982 serial_be.clone(),
983 RevocationEntry {
984 serial_be,
985 revocation_date,
986 reason_code,
987 },
988 );
989 }
990 Ok(CrlIndex {
991 issuer,
992 this_update,
993 next_update,
994 revoked,
995 })
996 }
997}
998
999pub struct ExporterBinding;
1002
1003impl ExporterBinding {
1004 pub fn derive(transport_secret: &[u8], label: &str, context: &[u8], length: usize) -> Vec<u8> {
1017 if transport_secret.is_empty() {
1018 panic!("ExporterBinding::derive: transport_secret must be non-empty");
1024 }
1025 let salt_str = format!("tf-tls-exporter:{}", label);
1026 let salt = salt_str.as_bytes();
1027
1028 use hmac::{Hmac, Mac};
1030 type HmacSha256 = Hmac<sha2::Sha256>;
1031 let mut mac = HmacSha256::new_from_slice(salt).expect("hmac key");
1032 mac.update(transport_secret);
1033 mac.update(context);
1034 let prk1 = mac.finalize().into_bytes();
1035
1036 let hk = hkdf::Hkdf::<sha2::Sha256>::new(None, &prk1);
1039 let mut out = vec![0u8; length];
1040 hk.expand(salt, &mut out).expect("HKDF expand");
1041 out
1042 }
1043}
1044
1045pub struct PostHandshakeReauth;
1048
1049impl PostHandshakeReauth {
1050 pub fn challenge() -> Vec<u8> {
1052 use rand::RngCore;
1053 let mut buf = vec![0u8; 32];
1054 rand::rngs::OsRng.fill_bytes(&mut buf);
1055 buf
1056 }
1057
1058 pub fn verify_response(challenge: &[u8], pubkey: &[u8; 32], signature: &[u8; 64]) -> bool {
1061 crate::crypto::ed25519_verify(pubkey, challenge, signature).is_ok()
1062 }
1063}