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 base64::engine::general_purpose::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
463use base64::Engine as _;
464
465#[derive(Clone, Debug)]
483pub struct X509Cert {
484 pub der: Vec<u8>,
485 pub subject: String,
486 pub serial_be: Vec<u8>,
487}
488
489impl X509Cert {
490 pub fn from_der(der: &[u8]) -> Result<Self, BridgeError> {
492 let (_, parsed) = X509Certificate::from_der(der)
493 .map_err(|e| BridgeError::InvalidInput(format!("X509Cert: {}", e)))?;
494 let serial_be = parsed.tbs_certificate.raw_serial().to_vec();
495 Ok(X509Cert {
496 der: der.to_vec(),
497 subject: parsed.subject().to_string(),
498 serial_be,
499 })
500 }
501
502 pub fn from_pem(pem: &str) -> Result<Self, BridgeError> {
504 let der = parse_single_pem(pem)
505 .map_err(|e| BridgeError::InvalidInput(format!("X509Cert PEM: {}", e)))?;
506 Self::from_der(&der)
507 }
508}
509
510#[derive(Clone, Debug, PartialEq, Eq)]
514pub enum OcspStatus {
515 Good,
516 Revoked,
517 Unknown,
518}
519
520pub trait OcspFetcher {
524 fn fetch(
525 &self,
526 cert: &X509Cert,
527 issuer: &X509Cert,
528 ocsp_url: &str,
529 ) -> Result<Vec<u8>, BridgeError>;
530}
531
532#[derive(Debug, thiserror::Error, PartialEq, Eq)]
534pub enum OcspError {
535 #[error("OCSP DER parse failed: {0}")]
536 Parse(String),
537 #[error("OCSP responder returned status code {0}")]
538 ResponderError(u8),
539 #[error("OCSP response is not a BasicOCSPResponse")]
540 NotBasic,
541 #[error("OCSP response thisUpdate={this_update} > now={now}")]
542 NotYetValid { this_update: i64, now: i64 },
543 #[error("OCSP response nextUpdate={next_update} < now={now}")]
544 Stale { next_update: i64, now: i64 },
545 #[error("OCSP response contained no SingleResponse entries")]
546 NoSingleResponses,
547}
548
549pub struct OcspCheck;
552
553impl OcspCheck {
554 pub fn query(
559 cert: &X509Cert,
560 issuer: &X509Cert,
561 fetcher: &dyn OcspFetcher,
562 ocsp_url: &str,
563 now_unix_seconds: i64,
564 ) -> Result<OcspStatus, BridgeError> {
565 let der = fetcher.fetch(cert, issuer, ocsp_url)?;
566 Self::parse_response(&der, now_unix_seconds).map_err(|e| match e {
567 OcspError::Parse(s) => BridgeError::InvalidInput(format!("OCSP: {}", s)),
568 OcspError::ResponderError(n) => {
569 BridgeError::Rejected(format!("OCSP responder error {}", n))
570 }
571 OcspError::NotBasic => BridgeError::Rejected("OCSP not BasicOCSPResponse".into()),
572 OcspError::NotYetValid { this_update, now } => {
573 BridgeError::Rejected(format!("OCSP thisUpdate={} > now={}", this_update, now))
574 }
575 OcspError::Stale { next_update, now } => {
576 BridgeError::Rejected(format!("OCSP nextUpdate={} < now={}", next_update, now))
577 }
578 OcspError::NoSingleResponses => {
579 BridgeError::Rejected("OCSP had no SingleResponse entries".into())
580 }
581 })
582 }
583
584 pub fn parse_response(der: &[u8], now_unix_seconds: i64) -> Result<OcspStatus, OcspError> {
588 let parsed = ocsp::parse_ocsp_response(der)?;
589 if parsed.response_status != 0 {
590 return Err(OcspError::ResponderError(parsed.response_status));
591 }
592 let basic = parsed.basic.ok_or(OcspError::NotBasic)?;
593 let single = basic
594 .single_responses
595 .first()
596 .ok_or(OcspError::NoSingleResponses)?;
597 if single.this_update > now_unix_seconds {
598 return Err(OcspError::NotYetValid {
599 this_update: single.this_update,
600 now: now_unix_seconds,
601 });
602 }
603 if let Some(next) = single.next_update {
604 if next < now_unix_seconds {
605 return Err(OcspError::Stale {
606 next_update: next,
607 now: now_unix_seconds,
608 });
609 }
610 }
611 Ok(single.status.clone())
612 }
613}
614
615pub mod ocsp {
623 use super::OcspError;
624 use super::OcspStatus;
625
626 #[derive(Clone, Debug)]
627 pub struct SingleResponse {
628 pub status: OcspStatus,
629 pub this_update: i64,
630 pub next_update: Option<i64>,
631 }
632
633 #[derive(Clone, Debug)]
634 pub struct BasicResponseData {
635 pub single_responses: Vec<SingleResponse>,
636 }
637
638 #[derive(Clone, Debug)]
639 pub struct OcspResponse {
640 pub response_status: u8,
641 pub basic: Option<BasicResponseData>,
642 }
643
644 const ID_PKIX_OCSP_BASIC: &[u8] = &[0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01];
646
647 pub fn parse_ocsp_response(der: &[u8]) -> Result<OcspResponse, OcspError> {
648 let outer = read_seq(der, 0).ok_or_else(|| OcspError::Parse("outer SEQUENCE".into()))?;
649 let mut p = outer.content_start;
650 let end = outer.content_start + outer.content_len;
651
652 let status_tlv =
654 read_tlv(der, p).ok_or_else(|| OcspError::Parse("responseStatus tag".into()))?;
655 if status_tlv.tag != 0x0a {
656 return Err(OcspError::Parse(format!(
657 "responseStatus expected ENUMERATED (0x0a), got 0x{:02x}",
658 status_tlv.tag
659 )));
660 }
661 if status_tlv.content_len != 1 {
662 return Err(OcspError::Parse("responseStatus len != 1".into()));
663 }
664 let response_status = der[status_tlv.content_start];
665 p = status_tlv.content_start + status_tlv.content_len;
666
667 let mut basic: Option<BasicResponseData> = None;
668 if p < end {
669 let rb_tlv = read_tlv(der, p).ok_or_else(|| OcspError::Parse("[0] tag".into()))?;
671 if rb_tlv.tag == 0xa0 {
672 let rb_seq = read_seq(der, rb_tlv.content_start)
673 .ok_or_else(|| OcspError::Parse("ResponseBytes SEQUENCE".into()))?;
674 let mut q = rb_seq.content_start;
675 let oid_tlv =
676 read_tlv(der, q).ok_or_else(|| OcspError::Parse("responseType OID".into()))?;
677 if oid_tlv.tag != 0x06 {
678 return Err(OcspError::Parse("responseType not OID".into()));
679 }
680 let oid_bytes =
681 &der[oid_tlv.content_start..oid_tlv.content_start + oid_tlv.content_len];
682 if oid_bytes != ID_PKIX_OCSP_BASIC {
683 } else {
685 q = oid_tlv.content_start + oid_tlv.content_len;
686 let response_octets = read_tlv(der, q)
687 .ok_or_else(|| OcspError::Parse("response OCTET STRING".into()))?;
688 if response_octets.tag != 0x04 {
689 return Err(OcspError::Parse("response not OCTET STRING".into()));
690 }
691 let basic_der = &der[response_octets.content_start
692 ..response_octets.content_start + response_octets.content_len];
693 basic = Some(parse_basic_response(basic_der)?);
694 }
695 }
696 }
697
698 Ok(OcspResponse {
699 response_status,
700 basic,
701 })
702 }
703
704 fn parse_basic_response(der: &[u8]) -> Result<BasicResponseData, OcspError> {
705 let basic_seq =
706 read_seq(der, 0).ok_or_else(|| OcspError::Parse("BasicOCSPResponse SEQ".into()))?;
707 let tbs = read_seq(der, basic_seq.content_start)
708 .ok_or_else(|| OcspError::Parse("tbsResponseData SEQ".into()))?;
709 let mut p = tbs.content_start;
712 let end = tbs.content_start + tbs.content_len;
713 if p < end && der[p] == 0xa0 {
715 let v = read_tlv(der, p).ok_or_else(|| OcspError::Parse("version".into()))?;
716 p = v.content_start + v.content_len;
717 }
718 if p < end {
720 let r = read_tlv(der, p).ok_or_else(|| OcspError::Parse("responderID".into()))?;
721 p = r.content_start + r.content_len;
722 }
723 if p < end {
725 let pa = read_tlv(der, p).ok_or_else(|| OcspError::Parse("producedAt".into()))?;
726 p = pa.content_start + pa.content_len;
727 }
728 let resp_seq =
730 read_seq(der, p).ok_or_else(|| OcspError::Parse("responses SEQUENCE OF".into()))?;
731 let mut single_responses: Vec<SingleResponse> = Vec::new();
732 let mut q = resp_seq.content_start;
733 let qend = resp_seq.content_start + resp_seq.content_len;
734 while q < qend {
735 let sr = read_seq(der, q).ok_or_else(|| OcspError::Parse("SingleResponse".into()))?;
736 single_responses.push(parse_single_response(
737 &der[sr.content_start..sr.content_start + sr.content_len],
738 )?);
739 q = sr.content_start + sr.content_len;
740 }
741 Ok(BasicResponseData { single_responses })
742 }
743
744 fn parse_single_response(der: &[u8]) -> Result<SingleResponse, OcspError> {
745 let mut p = 0usize;
748 let end = der.len();
749 let cert_id = read_seq(der, p).ok_or_else(|| OcspError::Parse("certID".into()))?;
751 p = cert_id.content_start + cert_id.content_len;
752 let cs = read_tlv(der, p).ok_or_else(|| OcspError::Parse("certStatus".into()))?;
754 let status = match cs.tag {
755 0x80 => OcspStatus::Good, 0xa1 => OcspStatus::Revoked, 0x82 => OcspStatus::Unknown, other => {
759 return Err(OcspError::Parse(format!(
760 "unknown certStatus tag 0x{:02x}",
761 other
762 )))
763 }
764 };
765 p = cs.content_start + cs.content_len;
766 let tu = read_tlv(der, p).ok_or_else(|| OcspError::Parse("thisUpdate".into()))?;
768 if tu.tag != 0x18 {
769 return Err(OcspError::Parse(format!(
770 "thisUpdate expected 0x18, got 0x{:02x}",
771 tu.tag
772 )));
773 }
774 let this_update =
775 parse_generalized_time(&der[tu.content_start..tu.content_start + tu.content_len])?;
776 p = tu.content_start + tu.content_len;
777 let mut next_update: Option<i64> = None;
779 if p < end && der[p] == 0xa0 {
780 let nu_outer =
781 read_tlv(der, p).ok_or_else(|| OcspError::Parse("nextUpdate [0]".into()))?;
782 let inner = read_tlv(der, nu_outer.content_start)
783 .ok_or_else(|| OcspError::Parse("nextUpdate inner".into()))?;
784 if inner.tag != 0x18 {
785 return Err(OcspError::Parse(format!(
786 "nextUpdate expected 0x18, got 0x{:02x}",
787 inner.tag
788 )));
789 }
790 next_update = Some(parse_generalized_time(
791 &der[inner.content_start..inner.content_start + inner.content_len],
792 )?);
793 }
794 Ok(SingleResponse {
795 status,
796 this_update,
797 next_update,
798 })
799 }
800
801 fn parse_generalized_time(bytes: &[u8]) -> Result<i64, OcspError> {
804 let s = std::str::from_utf8(bytes)
805 .map_err(|_| OcspError::Parse("generalized time non-utf8".into()))?;
806 if !s.ends_with('Z') {
807 return Err(OcspError::Parse(
808 "generalized time must be Zulu-suffixed".into(),
809 ));
810 }
811 let core = &s[..s.len() - 1];
812 if core.len() < 14 {
813 return Err(OcspError::Parse("generalized time too short".into()));
814 }
815 let y: i32 = core[0..4]
816 .parse()
817 .map_err(|_| OcspError::Parse("year".into()))?;
818 let m: u32 = core[4..6]
819 .parse()
820 .map_err(|_| OcspError::Parse("month".into()))?;
821 let d: u32 = core[6..8]
822 .parse()
823 .map_err(|_| OcspError::Parse("day".into()))?;
824 let hh: u32 = core[8..10]
825 .parse()
826 .map_err(|_| OcspError::Parse("hour".into()))?;
827 let mm: u32 = core[10..12]
828 .parse()
829 .map_err(|_| OcspError::Parse("min".into()))?;
830 let ss: u32 = core[12..14]
831 .parse()
832 .map_err(|_| OcspError::Parse("sec".into()))?;
833 Ok(ymdhms_to_unix(y, m, d, hh, mm, ss))
834 }
835
836 fn ymdhms_to_unix(y: i32, m: u32, d: u32, hh: u32, mm: u32, ss: u32) -> i64 {
837 let yy = if m <= 2 { y - 1 } else { y } as i64;
840 let era = if yy >= 0 { yy } else { yy - 399 } / 400;
841 let yoe = (yy - era * 400) as u64;
842 let mp = if m > 2 { m - 3 } else { m + 9 } as u64;
843 let doy = (153 * mp + 2) / 5 + (d as u64) - 1;
844 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
845 let days = era * 146_097 + doe as i64 - 719_468;
846 days * 86_400 + hh as i64 * 3600 + mm as i64 * 60 + ss as i64
847 }
848
849 pub(crate) struct Tlv {
852 pub tag: u8,
853 pub content_start: usize,
854 pub content_len: usize,
855 }
856
857 pub(crate) fn read_seq(buf: &[u8], pos: usize) -> Option<Tlv> {
858 let t = read_tlv(buf, pos)?;
859 if t.tag != 0x30 {
860 return None;
861 }
862 Some(t)
863 }
864
865 pub(crate) fn read_tlv(buf: &[u8], pos: usize) -> Option<Tlv> {
866 if pos >= buf.len() {
867 return None;
868 }
869 let tag = buf[pos];
870 let (len, header) = read_length(buf, pos + 1)?;
871 if pos + 1 + header + len > buf.len() {
872 return None;
873 }
874 Some(Tlv {
875 tag,
876 content_start: pos + 1 + header,
877 content_len: len,
878 })
879 }
880
881 fn read_length(buf: &[u8], pos: usize) -> Option<(usize, usize)> {
882 if pos >= buf.len() {
883 return None;
884 }
885 let b = buf[pos];
886 if b < 0x80 {
887 return Some((b as usize, 1));
888 }
889 let n = (b & 0x7f) as usize;
890 if n == 0 || n > 4 || pos + n >= buf.len() {
891 return None;
892 }
893 let mut len: usize = 0;
894 for i in 0..n {
895 len = (len << 8) | buf[pos + 1 + i] as usize;
896 }
897 Some((len, 1 + n))
898 }
899}
900
901#[derive(Clone, Debug, PartialEq, Eq)]
904pub struct RevocationEntry {
905 pub serial_be: Vec<u8>,
907 pub revocation_date: i64,
909 pub reason_code: Option<u8>,
911}
912
913#[derive(Debug, thiserror::Error, PartialEq, Eq)]
914pub enum CrlError {
915 #[error("CRL DER parse failed: {0}")]
916 Parse(String),
917}
918
919pub struct CrlIndex {
924 pub issuer: String,
925 pub this_update: i64,
926 pub next_update: Option<i64>,
927 revoked: std::collections::BTreeMap<Vec<u8>, RevocationEntry>,
928}
929
930impl CrlIndex {
931 pub fn issuer(&self) -> &str {
932 &self.issuer
933 }
934 pub fn len(&self) -> usize {
935 self.revoked.len()
936 }
937 pub fn is_empty(&self) -> bool {
938 self.revoked.is_empty()
939 }
940 pub fn is_revoked(&self, serial_be: &[u8]) -> Option<&RevocationEntry> {
944 let key = normalise_serial(serial_be);
945 self.revoked.get(&key)
946 }
947 pub fn entries(&self) -> impl Iterator<Item = &RevocationEntry> {
949 self.revoked.values()
950 }
951}
952
953fn normalise_serial(serial_be: &[u8]) -> Vec<u8> {
954 let mut s = serial_be;
955 while s.len() > 1 && s[0] == 0x00 {
956 s = &s[1..];
957 }
958 s.to_vec()
959}
960
961pub struct CrlCheck;
962
963impl CrlCheck {
964 pub fn load(crl_bytes: &[u8]) -> Result<CrlIndex, CrlError> {
965 use x509_parser::revocation_list::CertificateRevocationList;
966 let (_, crl) = CertificateRevocationList::from_der(crl_bytes)
967 .map_err(|e| CrlError::Parse(format!("{}", e)))?;
968 let issuer = crl.issuer().to_string();
969 let this_update = crl.last_update().timestamp();
970 let next_update = crl.next_update().map(|t| t.timestamp());
971 let mut revoked = std::collections::BTreeMap::new();
972 for r in crl.iter_revoked_certificates() {
973 let serial_be = normalise_serial(r.raw_serial());
974 let revocation_date = r.revocation_date.timestamp();
975 let reason_code = r
976 .extensions()
977 .iter()
978 .find_map(|ext| match ext.parsed_extension() {
979 x509_parser::extensions::ParsedExtension::ReasonCode(rc) => Some(rc.0),
980 _ => None,
981 });
982 revoked.insert(
983 serial_be.clone(),
984 RevocationEntry {
985 serial_be,
986 revocation_date,
987 reason_code,
988 },
989 );
990 }
991 Ok(CrlIndex {
992 issuer,
993 this_update,
994 next_update,
995 revoked,
996 })
997 }
998}
999
1000pub struct ExporterBinding;
1003
1004impl ExporterBinding {
1005 pub fn derive(transport_secret: &[u8], label: &str, context: &[u8], length: usize) -> Vec<u8> {
1018 if transport_secret.is_empty() {
1019 panic!("ExporterBinding::derive: transport_secret must be non-empty");
1025 }
1026 let salt_str = format!("tf-tls-exporter:{}", label);
1027 let salt = salt_str.as_bytes();
1028
1029 use hmac::{Hmac, Mac};
1031 type HmacSha256 = Hmac<sha2::Sha256>;
1032 let mut mac = HmacSha256::new_from_slice(salt).expect("hmac key");
1033 mac.update(transport_secret);
1034 mac.update(context);
1035 let prk1 = mac.finalize().into_bytes();
1036
1037 let hk = hkdf::Hkdf::<sha2::Sha256>::new(None, &prk1);
1040 let mut out = vec![0u8; length];
1041 hk.expand(salt, &mut out).expect("HKDF expand");
1042 out
1043 }
1044}
1045
1046pub struct PostHandshakeReauth;
1049
1050impl PostHandshakeReauth {
1051 pub fn challenge() -> Vec<u8> {
1053 use rand::RngCore;
1054 let mut buf = vec![0u8; 32];
1055 rand::rngs::OsRng.fill_bytes(&mut buf);
1056 buf
1057 }
1058
1059 pub fn verify_response(challenge: &[u8], pubkey: &[u8; 32], signature: &[u8; 64]) -> bool {
1062 crate::crypto::ed25519_verify(pubkey, challenge, signature).is_ok()
1063 }
1064}