Adds option/arg to allow minimal devices to make TLS insecure requests if needed, also adds compile.go build driver
This commit is contained in:
parent
b89caefb11
commit
ced950004a
7 changed files with 378 additions and 33 deletions
67
README.md
67
README.md
|
|
@ -39,12 +39,51 @@ Run the test suite with:
|
|||
go test ./...
|
||||
```
|
||||
|
||||
### Cross-compiling with compile.go
|
||||
|
||||
`compile.go` (repo root) is the supported build driver. It cross-compiles
|
||||
`./cmd/isthislegit` for a platform/arch matrix, always with
|
||||
`CGO_ENABLED=0`, so every target builds without a cross C toolchain:
|
||||
|
||||
```bash
|
||||
go run ./compile.go --platform=linux --arch=amd64
|
||||
```
|
||||
|
||||
| Flag | Default | Description |
|
||||
|--------------|---------|----------------------------------------------------------|
|
||||
| `--platform` | `linux` | Target OS (see vocabulary below). |
|
||||
| `--arch` | `amd64` | Target arch (see vocabulary below). |
|
||||
| `--static` | `true` | `true` for static (`CGO_ENABLED=0`), `false` for dynamic (`CGO_ENABLED=1`, host builds only unless `CC` points at a cross compiler). Both modes are stripped via `-ldflags="-s -w"`. |
|
||||
|
||||
Platform vocabulary (case-insensitive): `linux`; `mac`/`macos`/`osx`/`darwin`
|
||||
→ `darwin`; `windows`/`win` → `windows`; `bsd` → all three of `freebsd`,
|
||||
`openbsd`, `netbsd` in one run (or name a single BSD explicitly).
|
||||
|
||||
Arch vocabulary: raw `GOARCH` names plus aliases — `x64`/`x86-64` → `amd64`;
|
||||
`x86`/`i386`/`32bit` → `386`; `aarch64` → `arm64`; `arm`/`armv7`/`armv6`/`armv5`
|
||||
→ `arm` with matching `GOARM`. Unknown values exit 1, and combos the
|
||||
toolchain does not support (e.g. `darwin/386`) are refused with a clear
|
||||
error.
|
||||
|
||||
Output binaries are named `isthislegit-<goos>-<goarch>` (with a `vGOARM`
|
||||
suffix when applicable, e.g. `isthislegit-linux-armv7`, and `.exe` on
|
||||
Windows):
|
||||
|
||||
```bash
|
||||
go run ./compile.go --platform=linux --arch=amd64
|
||||
go run ./compile.go --platform=windows --arch=x64 --static=false
|
||||
go run ./compile.go --platform=bsd --arch=x64
|
||||
go run ./compile.go --platform=linux --arch=armv7 # embedded ARM (Miyoo Mini Plus)
|
||||
```
|
||||
|
||||
## Flags
|
||||
|
||||
| Flag | Description |
|
||||
|-----------------|---------------------------------------------------------------|
|
||||
| `-v, --verbose` | Append raw cert policy OIDs, TLS details, and RDAP JSON. |
|
||||
| `-j, --json` | Output the result summary as JSON for pipeline scripting. |
|
||||
| `-j, --json` | Output the result summary as JSON for scripting. |
|
||||
| `-k, --insecure`| Skip TLS verification for RDAP |
|
||||
| `--timeout=DUR` | Per-call deadline for DNS/TLS/RDAP (Go duration, default 5s). |
|
||||
| `--scale` | Print the scoring scale reference table and exit. |
|
||||
| `-h, --help` | Display usage and exit. |
|
||||
|
||||
|
|
@ -53,9 +92,27 @@ Examples:
|
|||
```bash
|
||||
isthislegit jdsoft.com jdsoftcareers.com
|
||||
isthislegit -j example.com suspect-example.com
|
||||
isthislegit --timeout=10s example.com suspect-example.com
|
||||
isthislegit --scale
|
||||
```
|
||||
|
||||
### Embedded systems (no CA certificates)
|
||||
|
||||
`isthislegit` runs on embedded ARM devices (verified on a Miyoo Mini Plus),
|
||||
but minimal firmware images often ship without a CA certificate store. In
|
||||
that case DNS resolution and certificate inspection still work, while
|
||||
verified HTTPS (RDAP) fails — the fail-closed error will say so directly
|
||||
(`x509: certificate signed by unknown authority`). Prefer installing
|
||||
`ca-certificates` and fixing the system clock where possible; otherwise use
|
||||
insecure mode:
|
||||
|
||||
```bash
|
||||
isthislegit -k jdsoft.com jdsoftcareers.com
|
||||
```
|
||||
|
||||
`--insecure` prints a warning and weakens results, so treat it as a last
|
||||
resort for devices without stored CA certificates.
|
||||
|
||||
## Example output
|
||||
|
||||
```text
|
||||
|
|
@ -104,8 +161,12 @@ VERDICT: LIKELY UNRELATED / MISCONFIGURED
|
|||
|
||||
Lookups fail closed: if RDAP dates or TLS data cannot be retrieved for
|
||||
either domain, the tool errors out instead of printing a verdict built on
|
||||
missing data. Fetches run concurrently under a 15s global budget with 5s
|
||||
per-call deadlines (3 attempts each).
|
||||
missing data, and the message names the underlying cause(s) (e.g. a TLS
|
||||
verification failure, which on minimal devices usually means missing CA
|
||||
certificates or a wrong system clock). Fetches run concurrently under a
|
||||
15s global budget with 5s per-call deadlines (3 attempts each, adjustable
|
||||
via `--timeout`); `--insecure` skips RDAP TLS verification as a last
|
||||
resort, with a warning.
|
||||
|
||||
## Project layout
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
|
@ -22,7 +24,7 @@ import (
|
|||
|
||||
const version = "1.01"
|
||||
|
||||
const perCall = 5 * time.Second
|
||||
const defaultPerCall = 5 * time.Second
|
||||
const globalTimeout = 15 * time.Second
|
||||
|
||||
func usage() string {
|
||||
|
|
@ -35,22 +37,28 @@ Usage:
|
|||
Options:
|
||||
-v, --verbose Output raw JSON responses from RDAP and full TLS cert details.
|
||||
-j, --json Output summary results in raw JSON format for scripting.
|
||||
-k, --insecure Skip TLS verification for RDAP (weakens results; use only
|
||||
on devices without CA certificates). Prints a warning.
|
||||
--timeout=DUR Per-call deadline for DNS/TLS/RDAP (Go duration, default 5s).
|
||||
--scale Show the scoring scale table and exit (no domains required).
|
||||
-h, --help Display this usage banner and exit.
|
||||
|
||||
Examples:
|
||||
isthislegit jdsoft.com jdsoftcareers.com
|
||||
isthislegit -j example.com suspect-example.com
|
||||
isthislegit --timeout=10s example.com suspect-example.com
|
||||
`, version)
|
||||
}
|
||||
|
||||
type domainResult struct {
|
||||
info score.DomainData
|
||||
cert *cert.Details
|
||||
rdap *rdap.Result
|
||||
info score.DomainData
|
||||
cert *cert.Details
|
||||
rdap *rdap.Result
|
||||
rdapErr error
|
||||
certErr error
|
||||
}
|
||||
|
||||
func fetchDomain(ctx context.Context, client *http.Client, domain string, now time.Time) domainResult {
|
||||
func fetchDomain(ctx context.Context, client *http.Client, domain string, now time.Time, perCall time.Duration) domainResult {
|
||||
res := domainResult{info: score.DomainData{Domain: domain}}
|
||||
var g errgroup.Group
|
||||
|
||||
|
|
@ -59,21 +67,17 @@ func fetchDomain(ctx context.Context, client *http.Client, domain string, now ti
|
|||
|
||||
g.Go(func() error {
|
||||
r, err := rdap.Query(ctx, client, domain, perCall)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rd = r
|
||||
return nil
|
||||
rd, res.rdapErr = r, err
|
||||
return err
|
||||
})
|
||||
g.Go(func() error {
|
||||
c, err := cert.InspectDomain(ctx, domain, perCall)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cd = c
|
||||
return nil
|
||||
cd, res.certErr = c, err
|
||||
return err
|
||||
})
|
||||
_ = g.Wait() // Fail-open per field; fail-closed verdict comes later.
|
||||
// Fail-open per field; the root causes are kept for the
|
||||
// fail-closed verdict gate below.
|
||||
_ = g.Wait()
|
||||
|
||||
res.rdap = rd
|
||||
res.cert = cd
|
||||
|
|
@ -109,7 +113,8 @@ func fetchDomain(ctx context.Context, client *http.Client, domain string, now ti
|
|||
}
|
||||
|
||||
func run() int {
|
||||
var verbose, jsonOut bool
|
||||
var verbose, jsonOut, insecure bool
|
||||
var perCall = defaultPerCall
|
||||
var positionals []string
|
||||
args := os.Args[1:]
|
||||
seenDashDash := false
|
||||
|
|
@ -119,11 +124,34 @@ func run() int {
|
|||
positionals = append(positionals, a)
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(a, "--timeout=") {
|
||||
d, err := time.ParseDuration(strings.TrimPrefix(a, "--timeout="))
|
||||
if err != nil || d <= 0 {
|
||||
fmt.Fprintf(os.Stderr, "[!] Error: Invalid --timeout value '%s'. Use a Go duration like 5s or 10s.\n", a)
|
||||
return 1
|
||||
}
|
||||
perCall = d
|
||||
continue
|
||||
}
|
||||
switch a {
|
||||
case "-v", "--verbose":
|
||||
verbose = true
|
||||
case "-j", "--json":
|
||||
jsonOut = true
|
||||
case "-k", "--insecure":
|
||||
insecure = true
|
||||
case "--timeout":
|
||||
if i+1 >= len(args) {
|
||||
fmt.Fprintln(os.Stderr, "[!] Error: --timeout requires a value (e.g. --timeout=10s).")
|
||||
return 1
|
||||
}
|
||||
i++
|
||||
d, err := time.ParseDuration(args[i])
|
||||
if err != nil || d <= 0 {
|
||||
fmt.Fprintf(os.Stderr, "[!] Error: Invalid --timeout value '%s'. Use a Go duration like 5s or 10s.\n", args[i])
|
||||
return 1
|
||||
}
|
||||
perCall = d
|
||||
case "--scale":
|
||||
fmt.Print(report.Scale(version))
|
||||
return 0
|
||||
|
|
@ -164,7 +192,7 @@ func run() int {
|
|||
for _, d := range []string{legit, suspect} {
|
||||
d := d
|
||||
dg.Go(func() error {
|
||||
if err := dns.CheckDNS(ctx, d); err != nil {
|
||||
if err := dns.CheckDNS(ctx, d, perCall); err != nil {
|
||||
return fmt.Errorf("domain '%s' failed DNS resolution (NXDOMAIN or offline). Aborting", d)
|
||||
}
|
||||
return nil
|
||||
|
|
@ -176,22 +204,43 @@ func run() int {
|
|||
}
|
||||
|
||||
client := &http.Client{}
|
||||
if insecure {
|
||||
fmt.Fprintln(os.Stderr, "[!] Warning: TLS verification disabled for RDAP (--insecure). Results are weaker.")
|
||||
client.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
|
||||
}
|
||||
now := time.Now()
|
||||
|
||||
var target, sus domainResult
|
||||
var fg errgroup.Group
|
||||
fg.Go(func() error {
|
||||
target = fetchDomain(ctx, client, legit, now)
|
||||
target = fetchDomain(ctx, client, legit, now, perCall)
|
||||
return nil
|
||||
})
|
||||
fg.Go(func() error {
|
||||
sus = fetchDomain(ctx, client, suspect, now)
|
||||
sus = fetchDomain(ctx, client, suspect, now, perCall)
|
||||
return nil
|
||||
})
|
||||
_ = fg.Wait()
|
||||
|
||||
if err := score.VerifyDataQuality(target.info, sus.info); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[!] Error: %v.\n", err)
|
||||
msg := fmt.Sprintf("[!] Error: %v.", err)
|
||||
var causes []string
|
||||
if target.rdapErr != nil {
|
||||
causes = append(causes, fmt.Sprintf("target RDAP: %v", target.rdapErr))
|
||||
}
|
||||
if target.certErr != nil {
|
||||
causes = append(causes, fmt.Sprintf("target TLS: %v", target.certErr))
|
||||
}
|
||||
if sus.rdapErr != nil {
|
||||
causes = append(causes, fmt.Sprintf("suspect RDAP: %v", sus.rdapErr))
|
||||
}
|
||||
if sus.certErr != nil {
|
||||
causes = append(causes, fmt.Sprintf("suspect TLS: %v", sus.certErr))
|
||||
}
|
||||
if len(causes) > 0 {
|
||||
msg += fmt.Sprintf(" Cause(s): %s.", strings.Join(causes, "; "))
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, msg)
|
||||
return 1
|
||||
}
|
||||
result := score.Score(target.info, sus.info)
|
||||
|
|
|
|||
219
compile.go
Normal file
219
compile.go
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
// Command compile is the build driver for isthislegit. It cross-compiles
|
||||
// ./cmd/isthislegit for the requested platform/arch matrix:
|
||||
//
|
||||
// go run ./compile.go --platform=linux --arch=amd64 --static=true
|
||||
//
|
||||
// Static builds (the default) are CGO-free so every target compiles
|
||||
// without a cross C toolchain. Dynamic builds (--static=false) enable
|
||||
// CGO and link libc dynamically; they require a C compiler for the
|
||||
// target (gcc for host builds, or CC set to a cross compiler).
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// platformAliases maps friendly platform names to GOOS values.
|
||||
// "bsd" fans out to freebsd, openbsd, and netbsd.
|
||||
var platformAliases = map[string][]string{
|
||||
"linux": {"linux"},
|
||||
"darwin": {"darwin"},
|
||||
"mac": {"darwin"},
|
||||
"macos": {"darwin"},
|
||||
"osx": {"darwin"},
|
||||
"windows": {"windows"},
|
||||
"win": {"windows"},
|
||||
"bsd": {"freebsd", "openbsd", "netbsd"},
|
||||
"freebsd": {"freebsd"},
|
||||
"openbsd": {"openbsd"},
|
||||
"netbsd": {"netbsd"},
|
||||
}
|
||||
|
||||
// archAlias resolves a friendly arch name to GOARCH plus optional GOARM.
|
||||
type archAlias struct {
|
||||
goarch string
|
||||
goarm string
|
||||
}
|
||||
|
||||
var archAliases = map[string]archAlias{
|
||||
"amd64": {goarch: "amd64"},
|
||||
"x64": {goarch: "amd64"},
|
||||
"x86-64": {goarch: "amd64"},
|
||||
"386": {goarch: "386"},
|
||||
"x86": {goarch: "386"},
|
||||
"i386": {goarch: "386"},
|
||||
"32bit": {goarch: "386"},
|
||||
"arm64": {goarch: "arm64"},
|
||||
"aarch64": {goarch: "arm64"},
|
||||
"arm": {goarch: "arm", goarm: "7"},
|
||||
"armv7": {goarch: "arm", goarm: "7"},
|
||||
"armv6": {goarch: "arm", goarm: "6"},
|
||||
"armv5": {goarch: "arm", goarm: "5"},
|
||||
}
|
||||
|
||||
// target is one GOOS/GOARCH(/GOARM) build.
|
||||
type target struct {
|
||||
goos string
|
||||
goarch string
|
||||
goarm string
|
||||
}
|
||||
|
||||
func resolvePlatforms(raw string) ([]string, error) {
|
||||
gooses, ok := platformAliases[strings.ToLower(strings.TrimSpace(raw))]
|
||||
if !ok {
|
||||
names := make([]string, 0, len(platformAliases))
|
||||
for name := range platformAliases {
|
||||
names = append(names, name)
|
||||
}
|
||||
return nil, fmt.Errorf("unknown platform %q (valid: %s)", raw, strings.Join(names, ", "))
|
||||
}
|
||||
return gooses, nil
|
||||
}
|
||||
|
||||
func resolveArch(raw string) (archAlias, error) {
|
||||
name := strings.ToLower(strings.TrimSpace(raw))
|
||||
if a, ok := archAliases[name]; ok {
|
||||
return a, nil
|
||||
}
|
||||
return archAlias{}, fmt.Errorf("unknown arch %q (valid examples: amd64, x64, arm64, aarch64, 386, x86, arm, armv7)", raw)
|
||||
}
|
||||
|
||||
// supported pairs "goos/goarch" from the local toolchain.
|
||||
func supportedPairs() (map[string]bool, error) {
|
||||
out, err := exec.Command("go", "tool", "dist", "list").Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot list toolchain targets: %w", err)
|
||||
}
|
||||
pairs := make(map[string]bool)
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
if line = strings.TrimSpace(line); line != "" {
|
||||
pairs[line] = true
|
||||
}
|
||||
}
|
||||
return pairs, nil
|
||||
}
|
||||
|
||||
func outputName(t target) string {
|
||||
name := fmt.Sprintf("isthislegit-%s-%s", t.goos, t.goarch)
|
||||
if t.goarm != "" {
|
||||
name += "v" + t.goarm
|
||||
}
|
||||
if t.goos == "windows" {
|
||||
name += ".exe"
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// isHost reports whether t matches the machine running compile.go.
|
||||
func (t target) isHost() bool {
|
||||
if t.goos != runtime.GOOS || t.goarch != runtime.GOARCH {
|
||||
return false
|
||||
}
|
||||
if t.goarch == "arm" {
|
||||
return t.goarm == goarmHost()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// goarmHost reads the host GOARM (empty unless arm, where it defaults to 7).
|
||||
func goarmHost() string {
|
||||
if arm := os.Getenv("GOARM"); arm != "" {
|
||||
return arm
|
||||
}
|
||||
return "7"
|
||||
}
|
||||
|
||||
func build(t target, cgo bool) error {
|
||||
// Both modes strip symbol tables and DWARF via the compiler; the
|
||||
// linkage (static vs dynamic) comes from CGO_ENABLED alone.
|
||||
args := []string{"build", `-ldflags=-s -w`}
|
||||
cgoEnabled := "0"
|
||||
if cgo {
|
||||
cgoEnabled = "1"
|
||||
}
|
||||
args = append(args, "-o", outputName(t), "./cmd/isthislegit")
|
||||
|
||||
cmd := exec.Command("go", args...)
|
||||
cmd.Env = append(os.Environ(),
|
||||
"GOOS="+t.goos,
|
||||
"GOARCH="+t.goarch,
|
||||
"CGO_ENABLED="+cgoEnabled,
|
||||
)
|
||||
if t.goarm != "" {
|
||||
cmd.Env = append(cmd.Env, "GOARM="+t.goarm)
|
||||
}
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
fmt.Printf("[*] GOOS=%s GOARCH=%s CGO_ENABLED=%s", t.goos, t.goarch, cgoEnabled)
|
||||
if t.goarm != "" {
|
||||
fmt.Printf(" GOARM=%s", t.goarm)
|
||||
}
|
||||
fmt.Printf(" -> %s\n", outputName(t))
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("build %s/%s failed: %w", t.goos, t.goarch, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func run() int {
|
||||
platformFlag := flag.String("platform", "linux", "target OS: linux, mac/darwin, windows/win, bsd (freebsd+openbsd+netbsd), or a single BSD")
|
||||
archFlag := flag.String("arch", "amd64", "target arch: amd64/x64, arm64/aarch64, 386/x86, arm/armv7, ...")
|
||||
staticFlag := flag.Bool("static", true, "true for static (CGO_ENABLED=0), false for dynamic (CGO_ENABLED=1, host builds); both stripped via -ldflags")
|
||||
flag.Parse()
|
||||
|
||||
gooses, err := resolvePlatforms(*platformFlag)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[!] Error: %v.\n", err)
|
||||
return 1
|
||||
}
|
||||
arch, err := resolveArch(*archFlag)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[!] Error: %v.\n", err)
|
||||
return 1
|
||||
}
|
||||
pairs, err := supportedPairs()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[!] Error: %v.\n", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
var targets []target
|
||||
for _, goos := range gooses {
|
||||
if !pairs[goos+"/"+arch.goarch] {
|
||||
fmt.Fprintf(os.Stderr, "[!] Error: target %s/%s is not supported by this toolchain.\n", goos, arch.goarch)
|
||||
return 1
|
||||
}
|
||||
targets = append(targets, target{goos: goos, goarch: arch.goarch, goarm: arch.goarm})
|
||||
}
|
||||
|
||||
// Dynamic builds need a C compiler for the target: gcc covers host
|
||||
// builds, while cross builds require CC set to a cross compiler.
|
||||
if !*staticFlag && os.Getenv("CC") == "" {
|
||||
for _, t := range targets {
|
||||
if !t.isHost() {
|
||||
fmt.Fprintf(os.Stderr, "[!] Error: --static=false needs CGO, which cannot cross-compile to %s/%s without a cross C compiler (set CC or use --static=true).\n", t.goos, t.goarch)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
built := 0
|
||||
for _, t := range targets {
|
||||
if err := build(t, !*staticFlag); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[!] Error: %v.\n", err)
|
||||
return 1
|
||||
}
|
||||
built++
|
||||
}
|
||||
fmt.Printf("[+] Built %d of %d target(s).\n", built, len(targets))
|
||||
return 0
|
||||
}
|
||||
|
||||
func main() {
|
||||
os.Exit(run())
|
||||
}
|
||||
|
|
@ -30,14 +30,15 @@ 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) error</pre></td></tr></table>
|
||||
<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 a 5 second deadline. Returns
|
||||
<tt>net.DefaultResolver.LookupIP</tt> with the given deadline. Returns
|
||||
<tt>nil</tt> when at least one IPv4 address resolves, an error otherwise.</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>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -12,17 +12,20 @@ symbols; this page documents behavior.</p>
|
|||
<font color="#859900"><h2>Arguments</h2></font>
|
||||
<p>Syntax: <tt>isthislegit [FLAGS] <legit_domain>
|
||||
<suspect_domain></tt>. Flags <tt>-v/--verbose</tt>,
|
||||
<tt>-j/--json</tt>, <tt>--scale</tt>, <tt>-h/--help</tt>, plus a
|
||||
<tt>-j/--json</tt>, <tt>-k/--insecure</tt> (skips RDAP TLS verification with
|
||||
a warning), <tt>--timeout=DUR</tt> (Go duration, default 5s),
|
||||
<tt>--scale</tt>, <tt>-h/--help</tt>, plus a
|
||||
<tt>--</tt> separator. Unknown flags and anything other than exactly two
|
||||
positional domains exit 1 with a usage message. Domains are normalized with
|
||||
<a href="dns.html#NormalizeDomain">dns.NormalizeDomain</a> and validated
|
||||
with <a href="dns.html#ValidSyntax">dns.ValidSyntax</a>.</p>
|
||||
with <a href="dns.html#ValidSyntax">dns.ValidSyntax</a>. Fail-closed errors
|
||||
include the underlying fetch cause(s).</p>
|
||||
|
||||
<font color="#859900"><h2>Budgets</h2></font>
|
||||
<table border="1" cellpadding="4">
|
||||
<tr><th>Budget</th><th>Value</th></tr>
|
||||
<tr><td>Global deadline (both domains)</td><td>15 seconds</td></tr>
|
||||
<tr><td>Per-call deadline (DNS, TLS, RDAP)</td><td>5 seconds</td></tr>
|
||||
<tr><td>Per-call deadline (DNS, TLS, RDAP)</td><td>5 seconds, adjustable with --timeout</td></tr>
|
||||
<tr><td>Attempts per call</td><td>3, with 1 second between retries</td></tr>
|
||||
</table>
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ go test ./...</pre></td></tr></table>
|
|||
<tr><th>Flag</th><th>Description</th></tr>
|
||||
<tr><td><tt>-v, --verbose</tt></td><td>Append raw cert policy OIDs, TLS details, and RDAP JSON.</td></tr>
|
||||
<tr><td><tt>-j, --json</tt></td><td>Output the result summary as JSON for scripting.</td></tr>
|
||||
<tr><td><tt>-k, --insecure</tt></td><td>Skip TLS verification for RDAP (weakens results; last resort for devices without CA certificates).</td></tr>
|
||||
<tr><td><tt>--timeout=DUR</tt></td><td>Per-call deadline for DNS/TLS/RDAP; Go duration, default 5s.</td></tr>
|
||||
<tr><td><tt>--scale</tt></td><td>Print the scoring scale table and exit.</td></tr>
|
||||
<tr><td><tt>-h, --help</tt></td><td>Display usage and exit.</td></tr>
|
||||
</table>
|
||||
|
|
@ -30,8 +32,10 @@ go test ./...</pre></td></tr></table>
|
|||
<font color="#859900"><h2>Fetch Pipeline</h2></font>
|
||||
<p>Both domains are fetched concurrently with
|
||||
<tt>golang.org/x/sync/errgroup</tt> under a 15 second global deadline; each
|
||||
DNS, TLS, and RDAP call carries a 5 second deadline and is retried up to 3
|
||||
times. Total run time equals the slower of the two domains.</p>
|
||||
DNS, TLS, and RDAP call carries a 5 second deadline (adjustable with
|
||||
<tt>--timeout</tt>) and is retried up to 3 times. Total run time equals the
|
||||
slower of the two domains. Fail-closed errors name the underlying
|
||||
cause(s).</p>
|
||||
<table border="0" cellpadding="8" width="100%"><tr><td bgcolor="#073642"><pre>parse args --> DNS pre-flight (target + suspect, parallel)
|
||||
--> fetch target = RDAP + TLS in parallel
|
||||
--> fetch suspect = RDAP + TLS in parallel
|
||||
|
|
@ -40,6 +44,14 @@ times. Total run time equals the slower of the two domains.</p>
|
|||
<tt>--help</tt>), <tt>1</tt> on bad flags, invalid domains, DNS failure, or
|
||||
failed data-quality verification.</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
|
||||
certificate inspection still work there, while verified HTTPS (RDAP) fails;
|
||||
the fail-closed error names the cause (<tt>x509: certificate signed by
|
||||
unknown authority</tt>). Install <tt>ca-certificates</tt> and fix the clock
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ func ValidSyntax(d string) bool {
|
|||
|
||||
// CheckDNS aborts early on NXDOMAIN / unresolvable names.
|
||||
// Mirrors bash check_dns (dig +short A). Uses the stdlib resolver.
|
||||
func CheckDNS(ctx context.Context, domain string) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
func CheckDNS(ctx context.Context, domain string, timeout time.Duration) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
addrs, err := net.DefaultResolver.LookupIP(ctx, "ip4", domain)
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue