tlsenum package probes a remote endpoint with one ClientHello per (version, cipher) pair via utls, so the checker can report the exact set the server accepts rather than only the suite Go's stdlib happens to negotiate. Probe accepts an Upgrader callback so STARTTLS dialects plug in without tlsenum learning about them; the checker bridges its existing dialect registry through upgraderFor.
90 lines
2.6 KiB
Go
90 lines
2.6 KiB
Go
package checker
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
|
|
sdk "git.happydns.org/checker-sdk-go/checker"
|
|
)
|
|
|
|
// Rules returns the full list of CheckRules exposed by the TLS checker.
|
|
// Each rule covers a single concern (reachability, handshake, chain, hostname,
|
|
// expiry, TLS version, STARTTLS advertisement, cipher suite, …) so the UI can
|
|
// surface a passing-list rather than a single aggregated code.
|
|
func Rules() []sdk.CheckRule {
|
|
return []sdk.CheckRule{
|
|
&endpointsDiscoveredRule{},
|
|
&reachabilityRule{},
|
|
&tlsHandshakeRule{},
|
|
&starttlsAdvertisedRule{},
|
|
&starttlsSupportedRule{},
|
|
&peerCertificateRule{},
|
|
&chainValidityRule{},
|
|
&hostnameMatchRule{},
|
|
&expiryRule{},
|
|
&tlsVersionRule{},
|
|
&cipherSuiteRule{},
|
|
&versionEnumerationRule{},
|
|
&weakCipherRule{},
|
|
}
|
|
}
|
|
|
|
// loadData fetches the TLS observation. On error, returns a single error
|
|
// state the caller should emit.
|
|
func loadData(ctx context.Context, obs sdk.ObservationGetter) (*TLSData, *sdk.CheckState) {
|
|
var data TLSData
|
|
if err := obs.Get(ctx, ObservationKeyTLSProbes, &data); err != nil {
|
|
return nil, &sdk.CheckState{
|
|
Status: sdk.StatusError,
|
|
Message: fmt.Sprintf("failed to load tls_probes observation: %v", err),
|
|
Code: "tls.observation_error",
|
|
}
|
|
}
|
|
return &data, nil
|
|
}
|
|
|
|
// sortedRefs returns the probe refs in deterministic order. Rules iterate
|
|
// this sorted list so CheckState output is stable.
|
|
func sortedRefs(data *TLSData) []string {
|
|
refs := make([]string, 0, len(data.Probes))
|
|
for ref := range data.Probes {
|
|
refs = append(refs, ref)
|
|
}
|
|
sort.Strings(refs)
|
|
return refs
|
|
}
|
|
|
|
// subjectOf formats the UI-facing subject for a single probe.
|
|
func subjectOf(p TLSProbe) string {
|
|
return fmt.Sprintf("%s://%s", p.Type, p.Endpoint)
|
|
}
|
|
|
|
// metaOf returns a compact meta map to attach to a CheckState.
|
|
func metaOf(p TLSProbe) map[string]any {
|
|
m := map[string]any{
|
|
"type": p.Type,
|
|
"host": p.Host,
|
|
"port": p.Port,
|
|
"sni": p.SNI,
|
|
}
|
|
if p.TLSVersion != "" {
|
|
m["tls_version"] = p.TLSVersion
|
|
}
|
|
return m
|
|
}
|
|
|
|
// passState / infoState / unknownState helpers.
|
|
func passState(code, message string) sdk.CheckState {
|
|
return sdk.CheckState{Status: sdk.StatusOK, Code: code, Message: message}
|
|
}
|
|
func unknownState(code, message string) sdk.CheckState {
|
|
return sdk.CheckState{Status: sdk.StatusUnknown, Code: code, Message: message}
|
|
}
|
|
|
|
// emptyCaseState returns a single state describing "no probes to evaluate".
|
|
// Rules call this when len(data.Probes) == 0 to avoid returning an empty
|
|
// slice (see CheckRule.Evaluate contract).
|
|
func emptyCaseState(code string) sdk.CheckState {
|
|
return unknownState(code, "No TLS endpoints have been discovered for this target yet.")
|
|
}
|