// Package checker implements the DANE/TLSA checker for happyDomain. // // This checker is bound to the svcs.TLSAs service. Collect takes the TLSA // records the user published (or plans to publish) for the service, derives // one TLS endpoint per distinct (port, proto, base name), and declares those // endpoints as tls.endpoint.v1 discovery entries. checker-tls then probes // them; on the next evaluation, this checker reads the related TLS probes // via obs.GetRelated and verifies each TLSA record matches the certificate // chain the probe observed. // // The user-visible contract matches what DANE deployers expect: // // - Usage 0 (PKIX-TA) / 1 (PKIX-EE): also require the PKIX chain to be // publicly trusted. // - Usage 2 (DANE-TA) / 3 (DANE-EE): trust the TLSA as the anchor; PKIX // validity is informational. // - Selector 0 (Cert) / 1 (SPKI) and matching types 0/1/2 (Full/SHA-256/ // SHA-512) are matched against the chain slot implied by the usage. package checker import "time" // ObservationKeyDANE is the observation key this checker writes. const ObservationKeyDANE = "dane_checks" // Option ids on CheckerOptions. const ( // OptionService is auto-filled by the happyDomain host with the // svcs.TLSAs service payload this checker is bound to. OptionService = "service" // OptionDomain is auto-filled with the domain apex. TLSA owner names // in the service are relative to this apex. OptionDomain = "domain_name" // OptionSubdomain is the optional sub-zone under which the TLSAs // service lives (matches the svcs.TLSAs analyzer's subdomain bucket). OptionSubdomain = "subdomain" // OptionProbeTimeoutMs is how long each underlying TLS probe is allowed. // Passed through to checker-tls verbatim via the discovery entry options. OptionProbeTimeoutMs = "probeTimeoutMs" // OptionSTARTTLS is an optional per-endpoint STARTTLS hint keyed by // "/" โ†’ RFC 6335 service name (e.g. "25/tcp" โ†’ "smtp", // "587/tcp" โ†’ "submission"). Common ports auto-map via a built-in table. OptionSTARTTLS = "starttls" ) // Severity constants mirror checker-tls. const ( SeverityCrit = "crit" SeverityWarn = "warn" SeverityInfo = "info" ) // TLSA field enum constants (RFC 6698 ยง2.1). const ( UsagePKIXTA uint8 = 0 UsagePKIXEE uint8 = 1 UsageDANETA uint8 = 2 UsageDANEEE uint8 = 3 SelectorCert uint8 = 0 SelectorSPKI uint8 = 1 MatchingFull uint8 = 0 MatchingSHA256 uint8 = 1 MatchingSHA512 uint8 = 2 ) // DANEData is the full payload the checker writes under ObservationKeyDANE. type DANEData struct { // Targets is one entry per (port, proto, basename) triplet extracted // from the TLSAs service. Targets []TargetResult `json:"targets"` CollectedAt time.Time `json:"collected_at"` } // TargetResult groups all TLSA records declared on a single endpoint and // carries enough context to render an actionable HTML row per endpoint. type TargetResult struct { // Owner is the fully qualified DANE owner name (_._.). Owner string `json:"owner"` // Host is the connection target (typically the base name the TLSA // records live under, or its MX/SRV target when relevant). Host string `json:"host"` Port uint16 `json:"port"` Proto string `json:"proto"` STARTTLS string `json:"starttls,omitempty"` // Ref ties this target to the tls.endpoint.v1 discovery entry the // checker emitted, so the rule can pick the matching RelatedObservation. Ref string `json:"ref"` // Records are the TLSA records declared for this endpoint. Records []TLSARecord `json:"records"` } // TLSARecord is a user-facing view of a single dns.TLSA record. type TLSARecord struct { Usage uint8 `json:"usage"` Selector uint8 `json:"selector"` MatchingType uint8 `json:"matching_type"` Certificate string `json:"certificate"` // lowercase hex }