Add tlsenum package and add version/cipher enumeration into the checker

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.
This commit is contained in:
nemunaire 2026-04-29 13:34:27 +07:00
commit a9f37c79cf
18 changed files with 1569 additions and 5 deletions

View file

@ -22,6 +22,7 @@ func (p *tlsProvider) Collect(ctx context.Context, opts sdk.CheckerOptions) (any
timeoutMs = DefaultProbeTimeoutMs
}
timeout := time.Duration(timeoutMs) * time.Millisecond
enumerate := sdk.GetBoolOption(opts, OptionEnumerateCiphers, false)
entries, warnings := contract.ParseEntries(raw)
for _, w := range warnings {
@ -40,15 +41,36 @@ func (p *tlsProvider) Collect(ctx context.Context, opts sdk.CheckerOptions) (any
var mu sync.Mutex
var wg sync.WaitGroup
sem := make(chan struct{}, MaxConcurrentProbes)
dispatch:
for _, e := range entries {
select {
case sem <- struct{}{}:
case <-ctx.Done():
break dispatch
}
wg.Add(1)
sem <- struct{}{}
go func() {
defer wg.Done()
defer func() { <-sem }()
pr := probe(ctx, e.Endpoint, timeout)
log.Printf("checker-tls: %s %s:%d → tls=%s handshake_ok=%t elapsed=%dms err=%q",
pr.Type, pr.Host, pr.Port, pr.TLSVersion, pr.TLSHandshakeOK, pr.ElapsedMS, pr.Error)
if enumerate && pr.TLSHandshakeOK {
enumRes, skipReason := enumerateEndpoint(ctx, e.Endpoint, enumerationBudget)
switch {
case enumRes != nil && enumRes.Skipped != "":
pr.Enum = enumRes
log.Printf("checker-tls: enum %s:%d → error: %s (duration=%dms)",
pr.Host, pr.Port, enumRes.Skipped, enumRes.DurationMS)
case enumRes != nil:
pr.Enum = enumRes
log.Printf("checker-tls: enum %s:%d → versions=%d duration=%dms",
pr.Host, pr.Port, len(enumRes.Versions), enumRes.DurationMS)
case skipReason != "":
log.Printf("checker-tls: enum %s:%d → skipped: %s",
pr.Host, pr.Port, skipReason)
}
}
mu.Lock()
probes[e.Ref] = pr
mu.Unlock()