Contextual age scoring with stacking and SAN discount, directional multi-resolver DNS validation, surfaced fetch errors, and GPLv3 licensing
This commit is contained in:
parent
ced950004a
commit
a43faca0b1
26 changed files with 1613 additions and 93 deletions
|
|
@ -65,5 +65,7 @@ already-parsed leaf certificate. Preferred entry point for tests.</p>
|
|||
<table border="0" cellpadding="8" width="100%"><tr><td bgcolor="#073642"><pre>func (d *Details) SANsString() string</pre></td></tr></table>
|
||||
<p>Joins SANs with <tt>", "</tt> for display, or returns
|
||||
<tt>"Unknown"</tt> when empty. Nil-receiver safe.</p>
|
||||
<hr>
|
||||
<font color="#b58900">Licensed under the GNU General Public License v3 - Mark Robillard Jr (MARKMENTAL)</font>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@
|
|||
</head>
|
||||
<body bgcolor="#002b36" text="#a3b8b9" link="#859900" vlink="#b58900">
|
||||
<font color="#b58900"><h1>Package dns</h1></font>
|
||||
<p>Domain normalization, syntax validation, and DNS pre-flight resolution
|
||||
checks. Source: <tt>internal/dns/dns.go</tt>.</p>
|
||||
<p>Domain normalization, syntax validation, DNS pre-flight resolution
|
||||
checks, and multi-resolver consistency cross-checks. Sources:
|
||||
<tt>internal/dns/dns.go</tt>, <tt>internal/dns/resolvers.go</tt>.</p>
|
||||
|
||||
<hr>
|
||||
<a name="NormalizeDomain"></a>
|
||||
|
|
@ -28,17 +29,69 @@ input before any network call.</p>
|
|||
dns.ValidSyntax("no-dot") // => false</pre></td></tr></table>
|
||||
|
||||
<hr>
|
||||
<a name="CheckDNS"></a>
|
||||
<font color="#859900"><h2>CheckDNS</h2></font>
|
||||
<table border="0" cellpadding="8" width="100%"><tr><td bgcolor="#073642"><pre>func CheckDNS(ctx context.Context, domain string, timeout time.Duration) error</pre></td></tr></table>
|
||||
<p>Aborts early on NXDOMAIN or unresolvable names via
|
||||
<tt>net.DefaultResolver.LookupIP</tt> with the given deadline. Returns
|
||||
<tt>nil</tt> when at least one IPv4 address resolves, an error otherwise.</p>
|
||||
<a name="DomainCheck"></a>
|
||||
<font color="#859900"><h2>DomainCheck</h2></font>
|
||||
<table border="0" cellpadding="8" width="100%"><tr><td bgcolor="#073642"><pre>type DomainCheck struct {
|
||||
SystemLive bool
|
||||
PublicLive bool
|
||||
Consistency Consistency
|
||||
}</pre></td></tr></table>
|
||||
<p>One domain's full probe: system liveness for pre-flight gating, public
|
||||
liveness for suppression attribution, and the aggregate consistency
|
||||
verdict. The pre-flight gate aborts on system failure and reuses these
|
||||
outcomes downstream, so each resolver is queried exactly once per
|
||||
domain.</p>
|
||||
|
||||
<hr>
|
||||
<a name="CheckDomain"></a>
|
||||
<font color="#859900"><h2>CheckDomain</h2></font>
|
||||
<table border="0" cellpadding="8" width="100%"><tr><td bgcolor="#073642"><pre>func CheckDomain(ctx context.Context, domain string, timeout time.Duration) DomainCheck</pre></td></tr></table>
|
||||
<p>Probes the system resolver and pinned public resolvers once. Use
|
||||
<tt>SystemLive</tt> for NXDOMAIN gating and <tt>Consistency</tt> for
|
||||
scoring and messaging.</p>
|
||||
|
||||
<hr>
|
||||
<a name="Consistency"></a>
|
||||
<font color="#859900"><h2>Consistency</h2></font>
|
||||
<table border="0" cellpadding="8" width="100%"><tr><td bgcolor="#073642"><pre>type Consistency struct {
|
||||
Checked bool
|
||||
Tampered bool
|
||||
Kind SplitKind
|
||||
Detail string
|
||||
}</pre></td></tr></table>
|
||||
<p>Verdict of a multi-resolver cross-check. <tt>Detail</tt> names the
|
||||
disagreeing resolvers and the split kind when tampered.</p>
|
||||
|
||||
<hr>
|
||||
<a name="SplitKind"></a>
|
||||
<font color="#859900"><h2>SplitKind</h2></font>
|
||||
<table border="0" cellpadding="8" width="100%"><tr><td bgcolor="#073642"><pre>const (
|
||||
SplitNone SplitKind = iota
|
||||
SplitSuppression
|
||||
SplitHorizon
|
||||
SplitNS
|
||||
)</pre></td></tr></table>
|
||||
<p>Split direction. Suppression (system dark, public live) signals local
|
||||
tampering; horizon (system live, public dark) is the expected split-horizon
|
||||
shape of internal names and never scores; NS marks delegation splits.</p>
|
||||
|
||||
<hr>
|
||||
<a name="CheckConsistency"></a>
|
||||
<font color="#859900"><h2>CheckConsistency</h2></font>
|
||||
<table border="0" cellpadding="8" width="100%"><tr><td bgcolor="#073642"><pre>func CheckConsistency(ctx context.Context, domain string, timeout time.Duration) Consistency</pre></td></tr></table>
|
||||
<p>Queries the system resolver plus pinned 1.1.1.1 and 8.8.8.8 servers in
|
||||
parallel (stdlib <tt>net.Resolver</tt> with a pinned dialer; filtered
|
||||
resolvers excluded by design). Flags NXDOMAIN-vs-live splits and NS
|
||||
delegation splits; raw A records are never compared, so CDN/GeoDNS churn
|
||||
stays quiet. Inconclusive resolvers abstain, and fewer than two conclusive
|
||||
votes is never tampering. Never fails closed.</p>
|
||||
<table border="1" cellpadding="4">
|
||||
<tr><th>Parameter</th><th>Description</th></tr>
|
||||
<tr><td><tt>ctx</tt></td><td>Parent context; the global 15 second budget.</td></tr>
|
||||
<tr><td><tt>domain</tt></td><td>Already-normalized domain to resolve.</td></tr>
|
||||
<tr><td><tt>timeout</tt></td><td>Per-call deadline (the CLI --timeout value).</td></tr>
|
||||
<tr><td><tt>domain</tt></td><td>Already-normalized domain to cross-check.</td></tr>
|
||||
<tr><td><tt>timeout</tt></td><td>Per-resolver deadline (the CLI --timeout value).</td></tr>
|
||||
</table>
|
||||
<hr>
|
||||
<font color="#b58900">Licensed under the GNU General Public License v3 - Mark Robillard Jr (MARKMENTAL)</font>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@
|
|||
<li><a href="report.html">Package report</a></li>
|
||||
<li><a href="main.html">Command main</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<font color="#b58900">Licensed under the GNU General Public License v3 - Mark Robillard Jr (MARKMENTAL)</font>
|
||||
</body>
|
||||
</noframes>
|
||||
</frameset>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,11 @@ include the underlying fetch cause(s).</p>
|
|||
|
||||
<font color="#859900"><h2>Flow</h2></font>
|
||||
<ol>
|
||||
<li>DNS pre-flight for both domains in parallel; any failure aborts.</li>
|
||||
<li>DNS pre-flight probes each unique domain once across the system and
|
||||
pinned resolvers; system failure aborts (with hijack-aware messaging when
|
||||
public resolvers resolve live). Outcomes are reused downstream, so no
|
||||
resolver is queried twice - identical domains are fetched once and the
|
||||
result shared.</li>
|
||||
<li>Per-domain fetch in parallel: RDAP
|
||||
(<a href="rdap.html#Query">rdap.Query</a>) plus TLS
|
||||
(<a href="cert.html#InspectDomain">cert.InspectDomain</a>) concurrently.
|
||||
|
|
@ -51,5 +55,7 @@ plus <a href="report.html#Verbose">report.Verbose</a> when verbose.</li>
|
|||
output, and <tt>--scale</tt> header. Build target:
|
||||
<tt>CGO_ENABLED=0 go build -ldflags="-s -w" -o isthislegit
|
||||
./cmd/isthislegit</tt>.</p>
|
||||
<hr>
|
||||
<font color="#b58900">Licensed under the GNU General Public License v3 - Mark Robillard Jr (MARKMENTAL)</font>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,11 @@
|
|||
<ul>
|
||||
<li><a href="dns.html#NormalizeDomain" target="content">NormalizeDomain</a></li>
|
||||
<li><a href="dns.html#ValidSyntax" target="content">ValidSyntax</a></li>
|
||||
<li><a href="dns.html#CheckDNS" target="content">CheckDNS</a></li>
|
||||
<li><a href="dns.html#DomainCheck" target="content">DomainCheck</a></li>
|
||||
<li><a href="dns.html#CheckDomain" target="content">CheckDomain</a></li>
|
||||
<li><a href="dns.html#SplitKind" target="content">SplitKind</a></li>
|
||||
<li><a href="dns.html#Consistency" target="content">Consistency</a></li>
|
||||
<li><a href="dns.html#CheckConsistency" target="content">CheckConsistency</a></li>
|
||||
</ul>
|
||||
<p><b>cert</b></p>
|
||||
<ul>
|
||||
|
|
@ -59,5 +63,7 @@
|
|||
<li><a href="report.html#JSON" target="content">JSON</a></li>
|
||||
<li><a href="report.html#Verbose" target="content">Verbose</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<font color="#b58900">Licensed under the GNU General Public License v3 - Mark Robillard Jr (MARKMENTAL)</font>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -44,6 +44,16 @@ cause(s).</p>
|
|||
<tt>--help</tt>), <tt>1</tt> on bad flags, invalid domains, DNS failure, or
|
||||
failed data-quality verification.</p>
|
||||
|
||||
<font color="#859900"><h2>DNS Consistency</h2></font>
|
||||
<p>Each domain is probed once by the system resolver plus pinned 1.1.1.1 and
|
||||
8.8.8.8 servers, and the outcomes are reused downstream. Only state is
|
||||
compared, never raw addresses: system-dark/public-live suppression splits
|
||||
and NS delegation splits flag tampering (+50 on the suspect), while
|
||||
system-live/public-dark split-horizon shapes and target-side splits stay
|
||||
display-only NOTEs. CDN/GeoDNS address churn stays quiet. Inconclusive
|
||||
resolvers abstain; filtered resolvers are excluded by design. The
|
||||
pre-flight gate aborts on system failure with hijack-aware messaging.</p>
|
||||
|
||||
<font color="#859900"><h2>Embedded Systems</h2></font>
|
||||
<p>The tool runs on embedded ARM devices (verified on a Miyoo Mini Plus),
|
||||
but minimal firmware often ships without a CA certificate store. DNS and
|
||||
|
|
@ -55,8 +65,14 @@ where possible, otherwise use <tt>-k/--insecure</tt> as a last resort.</p>
|
|||
<font color="#859900"><h2>Scoring Scale</h2></font>
|
||||
<table border="1" cellpadding="4">
|
||||
<tr><th>Signal</th><th>Points</th></tr>
|
||||
<tr><td>Suspect domain age < 30 days (CRITICAL)</td><td>+80</td></tr>
|
||||
<tr><td>Suspect domain age 30-89 days (WARNING)</td><td>+80</td></tr>
|
||||
<tr><td>Suspect < 30d (CRITICAL), stacked w/ signal</td><td>+80</td></tr>
|
||||
<tr><td>Suspect < 30d (CRITICAL), isolated</td><td>+40</td></tr>
|
||||
<tr><td>Suspect 30-89d (WARNING), stacked w/ signal</td><td>+40</td></tr>
|
||||
<tr><td>Suspect 30-89d (WARNING), isolated</td><td>+20</td></tr>
|
||||
<tr><td>Shared cert infrastructure (halves age tier)</td><td>x0.5</td></tr>
|
||||
<tr><td>Identical target/suspect (forced LEGITIMATE)</td><td>+0</td></tr>
|
||||
<tr><td>Resolution tampering: suppression split (suspect)</td><td>+50</td></tr>
|
||||
<tr><td>DNS split-horizon / target-side (display only)</td><td>+0</td></tr>
|
||||
<tr><td>Cert downgrade: target EV/OV vs suspect DV</td><td>+40</td></tr>
|
||||
<tr><td>Registrar mismatch</td><td>+30</td></tr>
|
||||
<tr><td>Same-class issuer difference (display only)</td><td>+0</td></tr>
|
||||
|
|
@ -79,5 +95,7 @@ where possible, otherwise use <tt>-k/--insecure</tt> as a last resort.</p>
|
|||
<li><a href="report.html"><b>report</b></a> - human, JSON, scale, and verbose renderers.</li>
|
||||
<li><a href="main.html"><b>main</b></a> - CLI entry point and fetch orchestration.</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<font color="#b58900">Licensed under the GNU General Public License v3 - Mark Robillard Jr (MARKMENTAL)</font>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -65,5 +65,7 @@ Non-2xx status and invalid JSON are errors.</p>
|
|||
<p>Best-effort registrar name, or <tt>"Unknown"</tt>. Prefers entities with
|
||||
the <tt>registrar</tt> role, falling back to the first vCard
|
||||
<tt>fn</tt> property. Nil-safe.</p>
|
||||
<hr>
|
||||
<font color="#b58900">Licensed under the GNU General Public License v3 - Mark Robillard Jr (MARKMENTAL)</font>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -60,5 +60,7 @@ newline for pipeline use.</p>
|
|||
<p>Raw policy OIDs with classes, per-domain TLS details (issuer, subject,
|
||||
validity, SANs), and raw RDAP JSON for both domains. Missing data renders
|
||||
an explicit unavailable notice per section.</p>
|
||||
<hr>
|
||||
<font color="#b58900">Licensed under the GNU General Public License v3 - Mark Robillard Jr (MARKMENTAL)</font>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
</head>
|
||||
<body bgcolor="#002b36" text="#a3b8b9" link="#859900" vlink="#b58900">
|
||||
<font color="#b58900"><h1>Package score</h1></font>
|
||||
<p>Deterministic risk scoring: domain-age check (+80), certificate
|
||||
<p>Deterministic risk scoring: domain-age check (contextual tiers with
|
||||
stacking and shared-cert discount), certificate
|
||||
downgrade check (+40), and registrar mismatch (+30), mapped onto a
|
||||
four-tier verdict. Sources: <tt>internal/score/score.go</tt>,
|
||||
<tt>internal/score/date.go</tt>.</p>
|
||||
|
|
@ -56,11 +57,18 @@ The CLI aborts instead of printing a verdict built on missing data.</p>
|
|||
<a name="Score"></a>
|
||||
<font color="#859900"><h2>Score</h2></font>
|
||||
<table border="0" cellpadding="8" width="100%"><tr><td bgcolor="#073642"><pre>func Score(target, suspect DomainData) Result</pre></td></tr></table>
|
||||
<p>Applies the three signals. Suspect age under 30 days adds 80 with a
|
||||
CRITICAL flag; 30-89 days adds 80 with a WARNING flag. Target EV/OV versus
|
||||
<p>Applies the signals. Identical domains force score zero and LEGITIMATE
|
||||
while still attaching DNS NOTEs. Suspect age
|
||||
is contextual: the full tier (< 30d: 80, 30-89d: 40) needs company
|
||||
(registrar mismatch or cert downgrade), otherwise the next tier down;
|
||||
shared certificate infrastructure (either domain in the other's SANs,
|
||||
exactly or via parent wildcard) halves whatever remains. Target EV/OV versus
|
||||
suspect DV adds 40 as DOWNGRADE; a same-class issuer difference is a
|
||||
display-only NOTE (+0). Case-insensitive registrar mismatch adds 30,
|
||||
skipped when either side is unknown.</p>
|
||||
skipped when either side is unknown. Suspect-side DNS suppression and NS
|
||||
splits add 50; split-horizon shapes and anything target-side are
|
||||
display-only NOTEs (+0). Identical domains force score zero and LEGITIMATE
|
||||
while still attaching DNS NOTEs.</p>
|
||||
<table border="0" cellpadding="8" width="100%"><tr><td bgcolor="#073642"><pre>r := score.Score(target, suspect)
|
||||
// r.Score, r.Findings, r.Verdict, r.AgeFlag</pre></td></tr></table>
|
||||
|
||||
|
|
@ -84,5 +92,7 @@ sentinel or garbage value fails by design.</p>
|
|||
<table border="0" cellpadding="8" width="100%"><tr><td bgcolor="#073642"><pre>func AgeDays(ymd string, now time.Time) (days int, ok bool)</pre></td></tr></table>
|
||||
<p>Whole days between <tt>ymd</tt> and <tt>now</tt>; <tt>ok</tt> is false
|
||||
when <tt>ymd</tt> is unparseable.</p>
|
||||
<hr>
|
||||
<font color="#b58900">Licensed under the GNU General Public License v3 - Mark Robillard Jr (MARKMENTAL)</font>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue