package checker import ( "context" "strings" sdk "git.happydns.org/checker-sdk-go/checker" tls "git.happydns.org/checker-tls/checker" ) // usageCoherentRule flags TLSA records whose declared usage contradicts the // chain slot their hash actually matches, typically a record published as // usage 1 or 3 (end-entity) whose hash in fact matches an intermediate. // That is almost always a publisher error: the intended usage was 0 or 2. type usageCoherentRule struct{} func (r *usageCoherentRule) Name() string { return "dane.usage_coherent" } func (r *usageCoherentRule) Description() string { return "Flags TLSA records whose declared usage does not match the chain slot they actually hash (e.g. usage 3 matching an intermediate)." } func (r *usageCoherentRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState { rc := loadRuleContext(ctx, obs) if rc.err != nil { return []sdk.CheckState{observationErrorState(rc.err)} } var out []sdk.CheckState tested := 0 for _, t := range rc.data.Targets { probe := rc.probes[t.Ref] if !probeUsable(probe) || len(probe.Chain) < 2 { continue } tested++ warn := suspiciousUsage(t, probe) if warn != "" { out = append(out, sdk.CheckState{ Status: sdk.StatusWarn, Code: "dane_usage_incoherent", Subject: targetSubject(t), Message: warn, Meta: targetMeta(t), }) } } if len(out) == 0 { if tested == 0 { return []sdk.CheckState{{ Status: sdk.StatusUnknown, Code: "dane_usage_coherent_skipped", Message: "No multi-cert chain probed yet; cannot assess usage coherence.", }} } return []sdk.CheckState{{ Status: sdk.StatusOK, Code: "dane_usage_coherent_ok", Message: "End-entity TLSA records match end-entity certificates on every probed chain.", }} } return out } // suspiciousUsage returns a human-readable hint when a record hash matches a // chain slot that contradicts its declared usage (e.g. usage 3 whose hash // actually matches the intermediate), almost always a publisher error. Used // by both usageCoherentRule and the HTML report. func suspiciousUsage(t TargetResult, p *tls.TLSProbe) string { if p == nil || len(p.Chain) < 2 { return "" } for _, r := range t.Records { if r.Usage != UsageDANEEE && r.Usage != UsagePKIXEE { continue } for _, c := range p.Chain[1:] { cand, err := recordCandidate(r, c) if err != nil { continue } if strings.EqualFold(cand, r.Certificate) { return "A record declared with usage 1/3 (end-entity) actually matches an intermediate certificate. It should probably use usage 0 or 2 (trust-anchor) instead." } } } return "" }