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.
68 lines
2.2 KiB
Go
68 lines
2.2 KiB
Go
package checker
|
||
|
||
import (
|
||
"context"
|
||
"net"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"git.happydns.org/checker-tls/contract"
|
||
"git.happydns.org/checker-tls/tlsenum"
|
||
)
|
||
|
||
// enumerationProbeTimeout caps each individual sub-probe. It is intentionally
|
||
// shorter than the main probe timeout: a sweep does dozens of handshakes and
|
||
// most rejections come back in tens of ms, so 3s is enough to absorb a slow
|
||
// network without dragging the total cost.
|
||
const enumerationProbeTimeout = 3 * time.Second
|
||
|
||
// enumerateEndpoint runs a (version × cipher) sweep against an endpoint —
|
||
// direct TLS or STARTTLS — and returns the result in the wire-format consumed
|
||
// by rules. It returns (nil, "<reason>") to signal the sweep was deliberately
|
||
// skipped.
|
||
func enumerateEndpoint(ctx context.Context, ep contract.TLSEndpoint, totalBudget time.Duration) (*TLSEnumeration, string) {
|
||
host := strings.TrimSuffix(ep.Host, ".")
|
||
addr := net.JoinHostPort(host, strconv.Itoa(int(ep.Port)))
|
||
sni := ep.SNI
|
||
if sni == "" {
|
||
sni = host
|
||
}
|
||
|
||
upgrader, ok := upgraderFor(ep.STARTTLS, sni)
|
||
if !ok {
|
||
return nil, "unsupported starttls dialect: " + ep.STARTTLS
|
||
}
|
||
|
||
sweepCtx := ctx
|
||
if totalBudget > 0 {
|
||
var cancel context.CancelFunc
|
||
sweepCtx, cancel = context.WithTimeout(ctx, totalBudget)
|
||
defer cancel()
|
||
}
|
||
|
||
start := time.Now()
|
||
res, err := tlsenum.Enumerate(sweepCtx, addr, sni, tlsenum.EnumerateOptions{
|
||
ProbeTimeout: enumerationProbeTimeout,
|
||
Upgrader: upgrader,
|
||
})
|
||
elapsed := time.Since(start).Milliseconds()
|
||
if err != nil {
|
||
return &TLSEnumeration{Skipped: "enumeration error: " + err.Error(), DurationMS: elapsed}, ""
|
||
}
|
||
|
||
out := &TLSEnumeration{DurationMS: elapsed}
|
||
for _, v := range res.SupportedVersions {
|
||
ev := EnumVersion{Version: v, Name: tlsenum.VersionName(v)}
|
||
for _, c := range res.CiphersByVersion[v] {
|
||
ev.Ciphers = append(ev.Ciphers, EnumCipher{ID: c.ID, Name: c.Name})
|
||
}
|
||
out.Versions = append(out.Versions, ev)
|
||
}
|
||
return out, ""
|
||
}
|
||
|
||
// enumerationBudget is the upper bound we give one endpoint's sweep. ~50
|
||
// handshakes × enumerationProbeTimeout would be 2-3 minutes worst case; we
|
||
// cap at 60s so a black-holing target can't stall the whole collect run.
|
||
const enumerationBudget = 60 * time.Second
|