92 lines
2.6 KiB
Go
92 lines
2.6 KiB
Go
package checker
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
sdk "git.happydns.org/checker-sdk-go/checker"
|
|
)
|
|
|
|
// tlsProbeView is a permissive subset of checker-tls's probe payload;
|
|
// only fields the CAA rule needs are decoded so the TLS checker can
|
|
// evolve its schema independently.
|
|
type tlsProbeView struct {
|
|
Host string `json:"host,omitempty"`
|
|
Port uint16 `json:"port,omitempty"`
|
|
Endpoint string `json:"endpoint,omitempty"`
|
|
Type string `json:"type,omitempty"`
|
|
Issuer string `json:"issuer,omitempty"`
|
|
IssuerDN string `json:"issuer_dn,omitempty"`
|
|
IssuerAKI string `json:"issuer_aki,omitempty"`
|
|
NotAfter time.Time `json:"not_after,omitempty"`
|
|
ChainValid *bool `json:"chain_valid,omitempty"`
|
|
DNSNames []string `json:"dns_names,omitempty"`
|
|
Subject string `json:"subject,omitempty"`
|
|
}
|
|
|
|
// isWildcard reports whether the observed certificate covers at least
|
|
// one wildcard DNS name. Used to pick between the CAA "issue" and
|
|
// "issuewild" allow lists per RFC 8659 §4.3.
|
|
func (v *tlsProbeView) isWildcard() bool {
|
|
for _, n := range v.DNSNames {
|
|
if strings.HasPrefix(n, "*.") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (v *tlsProbeView) address() string {
|
|
if v.Endpoint != "" {
|
|
return v.Endpoint
|
|
}
|
|
if v.Host != "" && v.Port != 0 {
|
|
return net.JoinHostPort(v.Host, strconv.FormatUint(uint64(v.Port), 10))
|
|
}
|
|
return v.Host
|
|
}
|
|
|
|
// parseTLSRelated decodes a RelatedObservation into probes. Two
|
|
// payload shapes are accepted: the current {"probes": {ref: …}} map
|
|
// (filtered by r.Ref when set) and a bare top-level probe (back-compat).
|
|
// Returns nil when the payload is not a recognizable probe shape.
|
|
func parseTLSRelated(r sdk.RelatedObservation) []*tlsProbeView {
|
|
var keyed struct {
|
|
Probes map[string]tlsProbeView `json:"probes"`
|
|
}
|
|
if err := json.Unmarshal(r.Data, &keyed); err == nil && keyed.Probes != nil {
|
|
if r.Ref != "" {
|
|
if p, ok := keyed.Probes[r.Ref]; ok {
|
|
cp := p
|
|
return []*tlsProbeView{&cp}
|
|
}
|
|
}
|
|
out := make([]*tlsProbeView, 0, len(keyed.Probes))
|
|
for _, p := range keyed.Probes {
|
|
cp := p
|
|
out = append(out, &cp)
|
|
}
|
|
return out
|
|
}
|
|
var v tlsProbeView
|
|
if err := json.Unmarshal(r.Data, &v); err != nil {
|
|
return nil
|
|
}
|
|
if v.Host == "" && v.IssuerAKI == "" && v.IssuerDN == "" {
|
|
return nil
|
|
}
|
|
return []*tlsProbeView{&v}
|
|
}
|
|
|
|
// parseAllTLSRelated flattens a slice of RelatedObservations into one
|
|
// entry per endpoint.
|
|
func parseAllTLSRelated(related []sdk.RelatedObservation) []*tlsProbeView {
|
|
var out []*tlsProbeView
|
|
for _, r := range related {
|
|
out = append(out, parseTLSRelated(r)...)
|
|
}
|
|
return out
|
|
}
|