diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..91e27c5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +Dockerfile +certgraph +build/ +*.json \ No newline at end of file diff --git a/.gitignore b/.gitignore index c167f4f..f353dbc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -go.sum certgraph build/ *.json diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8735826 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM golang:alpine + +RUN apk add --update git make + +WORKDIR /src/certgraph +ADD . . + +ENV CGO_ENABLED=0 +RUN make install + +ENTRYPOINT [ "certgraph" ] \ No newline at end of file diff --git a/Makefile b/Makefile index 178312e..94f6985 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,18 @@ GIT_DATE := $(shell git log -1 --date=short --pretty='%cd' | tr -d -) GIT_HASH := $(shell git rev-parse HEAD) -BUILD_FLAGS := -ldflags "-X main.gitDate=$(GIT_DATE) -X main.gitHash=$(GIT_HASH)" +BUILD_FLAGS := -trimpath -ldflags "-w -s -X main.gitDate=$(GIT_DATE) -X main.gitHash=$(GIT_HASH)" PLATFORMS := linux/amd64 linux/386 linux/arm darwin/amd64 windows/amd64 windows/386 openbsd/amd64 SOURCES := $(shell find . -maxdepth 1 -type f -name "*.go") -ALL_SOURCES = $(shell find . -type f -name '*.go') +ALL_SOURCES = $(shell find . -type f -name '*.go') go.mod web/index_html.go temp = $(subst /, ,$@) os = $(word 1, $(temp)) arch = $(word 2, $(temp)) ext = $(shell if [ "$(os)" = "windows" ]; then echo ".exe"; fi) -.PHONY: all release fmt clean serv $(PLATFORMS) +.PHONY: all release fmt clean serv $(PLATFORMS) docker check all: certgraph @@ -26,6 +26,12 @@ CGO_ENABLED=0 GOOS=$(os) GOARCH=$(arch) go build $(BUILD_FLAGS) -o 'build/bin/$(os)/$(arch)/certgraph$(ext)' $(SOURCES) mkdir -p build/$(GIT_DATE)/; cd build/bin/$(os)/$(arch)/; zip -r ../../../$(GIT_DATE)/certgraph-$(os)-$(arch)-$(GIT_DATE).zip .; cd ../../../ +web/index_html.go: docs/index.html + go generate -x ./... + +docker: Dockerfile $(ALL_SOURCES) + docker build -t lanrat/certgraph . + fmt: gofmt -s -w -l . @@ -33,7 +39,21 @@ go install $(BUILD_FLAGS) clean: - rm -r certgraph build/ + rm -rf certgraph build/ web/index_html.go -serv: - (cd docs; python -m SimpleHTTPServer) +check: | lint check1 check2 + +check1: + golangci-lint run + +check2: + staticcheck -f stylish -checks all ./... + +lint: + golint ./... + +serv: certgraph + ./certgraph --serve 127.0.0.1:8080 + +updateMod: + go get -u diff --git a/README.md b/README.md index e83f643..02f2602 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # CertGraph -### A tool to crawl the graph of certificate Alternate Names + +## A tool to crawl the graph of certificate Alternate Names CertGraph crawls SSL certificates creating a directed graph where each domain is a node and the certificate alternative names for that domain's certificate are the edges to other domain nodes. New domains are printed as they are found. In Detailed mode upon completion the Graph's adjacency list is printed. @@ -10,57 +11,62 @@ [Blog post with more information](https://lanrat.com/certgraph/) ## Usage -``` + +```console Usage of ./certgraph: [OPTION]... HOST... - https://github.com/lanrat/certgraph + https://github.com/lanrat/certgraph OPTIONS: + -apex + for every domain found, add the apex domain of the domain's parent -cdn - include certificates from CDNs + include certificates from CDNs -ct-expired - include expired certificates in certificate transparency search + include expired certificates in certificate transparency search -ct-subdomains - include sub-domains in certificate transparency search + include sub-domains in certificate transparency search -depth uint - maximum BFS depth to go (default 5) + maximum BFS depth to go (default 5) -details - print details about the domains crawled + print details about the domains crawled + -dns + check for DNS records to determine if domain is registered -driver string - driver to use [crtsh, google, http, smtp] (default "http") + driver to use [crtsh, google, http, smtp] (default "http") -json - print the graph as json, can be used for graph in web UI - -ns - check for NS records to determine if domain is registered + print the graph as json, can be used for graph in web UI -parallel uint - number of certificates to retrieve in parallel (default 10) + number of certificates to retrieve in parallel (default 10) -sanscap int - maximum number of uniq TLD+1 domains in certificate to include, 0 has no limit (default 80) + maximum number of uniq apex domains in certificate to include, 0 has no limit (default 80) -save string - save certs to folder in PEM format + save certs to folder in PEM format + -serve string + address:port to serve html UI on -timeout uint - tcp timeout in seconds (default 10) - -tldplus1 - for every domain found, add tldPlus1 of the domain's parent + tcp timeout in seconds (default 10) + -updatepsl + Update the default Public Suffix List -verbose - verbose logging + verbose logging -version - print version and exit + print version and exit ``` ## Drivers CertGraph has multiple options for querying SSL certificates. The driver is responsible for retrieving the certificates for a given domain. Currently there are the following drivers: - * **http** this is the default driver which works by connecting to the hosts over HTTPS and retrieving the certificates from the SSL connection - - * **smtp** like the *http* driver, but connects over port 25 and issues the *starttls* command to retrieve the certificates from the SSL connection +* **http** this is the default driver which works by connecting to the hosts over HTTPS and retrieving the certificates from the SSL connection - * **crtsh** this driver searches Certificate Transparency logs via [crt.sh](https://crt.sh/). No packets are sent to any of the domains when using this driver +* **smtp** like the *http* driver, but connects over port 25 and issues the *starttls* command to retrieve the certificates from the SSL connection - * **google** this is another Certificate Transparency driver that behaves like *crtsh* but uses the [Google Certificate Transparency Lookup Tool](https://transparencyreport.google.com/https/certificates) +* **crtsh** this driver searches Certificate Transparency logs via [crt.sh](https://crt.sh/). No packets are sent to any of the domains when using this driver +* **google** this is another Certificate Transparency driver that behaves like *crtsh* but uses the [Google Certificate Transparency Lookup Tool](https://transparencyreport.google.com/https/certificates) ## Example -``` + +```console $ ./certgraph -details eff.org eff.org 0 Good 42E3E4605D8BB4608EB64936E2176A98B97EBF2E0F8F93A64A6640713C7D4325 maps.eff.org 1 Good 42E3E4605D8BB4608EB64936E2176A98B97EBF2E0F8F93A64A6640713C7D4325 @@ -69,32 +75,54 @@ atlas.eff.org 1 Good 42E3E4605D8BB4608EB64936E2176A98B97EBF2E0F8F93A64A6640713C7D4325 kittens.eff.org 1 Good 42E3E4605D8BB4608EB64936E2176A98B97EBF2E0F8F93A64A6640713C7D4325 ``` + The above output represents the adjacency list for the graph for the root domain `eff.org`. The adjacency list is in the form: `Node Depth Status Cert-Fingerprint` ## [Releases](https://github.com/lanrat/certgraph/releases) -Precompiled releases will occasionally be uploaded to the [releases github page](https://github.com/lanrat/certgraph/releases). https://github.com/lanrat/certgraph/releases +Pre-compiled releases will occasionally be uploaded to the [releases github page](https://github.com/lanrat/certgraph/releases). [https://github.com/lanrat/certgraph/releases](https://github.com/lanrat/certgraph/releases) -Also available in [BlackArch](https://blackarch.org). +### [Docker](https://hub.docker.com/r/lanrat/certgraph/) + +CertGraph is an automated build on the Docker Hub! + +```console +$ docker run --rm -it lanrat/certgraph example.com +example.com +www.example.net +www.example.org +www.example.com +example.org +example.net +example.edu +www.example.edu +``` + +### Linux Distributions + +* [BlackArch](https://blackarch.org) +* [Kali Linux](https://www.kali.org/) ## Compiling -To compile certgraph you must have a working go 1.11 or newer compiler on your system, as certgraph makes use of go's modules for dependencies. +To compile certgraph you must have a working go 1.13 or newer compiler on your system, as certgraph makes use of go's modules for dependencies. To compile for the running system compilation is as easy as running make -``` + +```console certgraph$ make go build -o certgraph certgraph.go ``` Alternatively you can use `go get` to install with this one-liner: -``` + +```console go get -u github.com/lanrat/certgraph ``` ## [Web UI](https://lanrat.github.io/certgraph/) -A web UI is provided in the docs folder and is accessible at the github pages url [https://lanrat.github.io/certgraph/](https://lanrat.github.io/certgraph/). +A web UI is provided in the docs folder and is accessible at the github pages url [https://lanrat.github.io/certgraph/](https://lanrat.github.io/certgraph/), or can be run from the embedded web server by calling `certgraph --serve 127.0.0.1:8080`. The web UI takes the output provided with the `-json` flag. The JSON graph can be sent to the web interface as an uploaded file, remote URL, or as the query string using the data variable. @@ -111,3 +139,23 @@ [![whitehouse.gov graph](https://cloud.githubusercontent.com/assets/164192/20861407/4775ff26-b944-11e6-888c-4d93e3333494.png)](https://lanrat.github.io/certgraph/?data=https://gist.githubusercontent.com/lanrat/96c47dfee0faaaad633cc830b7e3b997/raw/3c79fed837cb3202e220de21d2a8eb128f4bbd9f/whitehouse.json) +## BygoneSSL detection + +### Self Detection + +CertGraph can be used to detect [BygoneSSL](https://insecure.design) DoS with the following options. CT-DRIVER can be any Certificate Transparency capable driver. +Provide all known input domains you own. If any domains you do not own are printed, then you are vulnerable. + +```console +certgraph -depth 1 -driver CT-DRIVER -ct-subdomains -cdn -apex [DOMAIN]... +``` + +### Bug Bounty + +If you want to find a vulnerable site that has a bug bounty, certgraph can be used with the following options and any driver. But you will have better luck with a non Certificate Transparency driver to ensure that the certificates in question are actually in use + +```console +certgraph -cdn -dns -apex [DOMAIN]... +``` + +And domains that print `* Missing DNS for` have vulnerable certificates that should be rotated. diff --git a/certgraph.go b/certgraph.go index f45d0ce..74c1bad 100644 --- a/certgraph.go +++ b/certgraph.go @@ -17,6 +17,7 @@ "github.com/lanrat/certgraph/driver/http" "github.com/lanrat/certgraph/driver/smtp" "github.com/lanrat/certgraph/graph" + "github.com/lanrat/certgraph/web" ) var ( @@ -28,6 +29,7 @@ var certDriver driver.Driver // config & flags +// TODO move driver options to own struct var config struct { timeout time.Duration verbose bool @@ -41,10 +43,11 @@ includeCTExpired bool cdn bool maxSANsSize int - tldPlus1 bool + apex bool updatePSL bool checkDNS bool printVersion bool + serve string } func init() { @@ -55,16 +58,18 @@ flag.StringVar(&config.driver, "driver", "http", fmt.Sprintf("driver to use [%s]", strings.Join(driver.Drivers, ", "))) flag.BoolVar(&config.includeCTSubdomains, "ct-subdomains", false, "include sub-domains in certificate transparency search") flag.BoolVar(&config.includeCTExpired, "ct-expired", false, "include expired certificates in certificate transparency search") - flag.IntVar(&config.maxSANsSize, "sanscap", 80, "maximum number of uniq TLD+1 domains in certificate to include, 0 has no limit") + flag.IntVar(&config.maxSANsSize, "sanscap", 80, "maximum number of uniq apex domains in certificate to include, 0 has no limit") flag.BoolVar(&config.cdn, "cdn", false, "include certificates from CDNs") flag.BoolVar(&config.checkDNS, "dns", false, "check for DNS records to determine if domain is registered") - flag.BoolVar(&config.tldPlus1, "tldplus1", false, "for every domain found, add tldPlus1 of the domain's parent") + flag.BoolVar(&config.apex, "apex", false, "for every domain found, add the apex domain of the domain's parent") flag.BoolVar(&config.updatePSL, "updatepsl", false, "Update the default Public Suffix List") flag.UintVar(&config.maxDepth, "depth", 5, "maximum BFS depth to go") flag.UintVar(&config.parallel, "parallel", 10, "number of certificates to retrieve in parallel") flag.BoolVar(&config.details, "details", false, "print details about the domains crawled") flag.BoolVar(&config.printJSON, "json", false, "print the graph as json, can be used for graph in web UI") flag.StringVar(&config.savePath, "save", "", "save certs to folder in PEM format") + flag.StringVar(&config.serve, "serve", "", "address:port to serve html UI on") + flag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage of %s: [OPTION]... HOST...\n\thttps://github.com/lanrat/certgraph\nOPTIONS:\n", os.Args[0]) flag.PrintDefaults() @@ -78,6 +83,11 @@ if config.printVersion { fmt.Println(version()) return + } + + if len(config.serve) > 0 { + err := web.Serve(config.serve) + e(err) } // print usage if no domain passed @@ -108,12 +118,12 @@ d := strings.ToLower(domain) if len(d) > 0 { startDomains = append(startDomains, cleanInput(d)) - if config.tldPlus1 { - tldPlus1, err := dns.TLDPlus1(domain) + if config.apex { + apexDomain, err := dns.ApexDomain(domain) if err != nil { continue } - startDomains = append(startDomains, tldPlus1) + startDomains = append(startDomains, apexDomain) } } } @@ -148,6 +158,7 @@ // setDriver sets the driver variable for the provided driver string and does any necessary driver prep work // TODO make config generic and move this to driver module +// TODO support multi-driver func setDriver(driver string) error { var err error switch driver { @@ -160,7 +171,7 @@ case "smtp": certDriver, err = smtp.Driver(config.timeout, config.savePath) default: - return fmt.Errorf("Unknown driver name: %s", config.driver) + return fmt.Errorf("unknown driver name: %s", config.driver) } return err } @@ -244,13 +255,13 @@ for _, neighbor := range certGraph.GetDomainNeighbors(domainNode.Domain, config.cdn, config.maxSANsSize) { wg.Add(1) domainNodeInputChan <- graph.NewDomainNode(neighbor, domainNode.Depth+1) - if config.tldPlus1 { - tldPlus1, err := dns.TLDPlus1(neighbor) + if config.apex { + apexDomain, err := dns.ApexDomain(neighbor) if err != nil { continue } wg.Add(1) - domainNodeInputChan <- graph.NewDomainNode(tldPlus1, domainNode.Depth+1) + domainNodeInputChan <- graph.NewDomainNode(apexDomain, domainNode.Depth+1) } } }(domainNode) @@ -341,7 +352,7 @@ domainNode.AddCertFingerprint(certNode.Fingerprint, certDriver.GetName()) } - // we dont process any other certificates returned, they will be collected + // we don't process any other certificates returned, they will be collected // when we process the related domains } @@ -354,7 +365,7 @@ if config.checkDNS && !domainNode.HasDNS { // TODO print this in a better way // TODO for debugging - realDomain, _ := dns.TLDPlus1(domainNode.Domain) + realDomain, _ := dns.ApexDomain(domainNode.Domain) fmt.Fprintf(os.Stdout, "* Missing DNS for: %s\n", realDomain) } diff --git a/dns/ns.go b/dns/ns.go index 5cd2635..0214684 100644 --- a/dns/ns.go +++ b/dns/ns.go @@ -1,3 +1,4 @@ +// Package dns adds utility functions for performing dns queries package dns import ( @@ -26,7 +27,7 @@ } // HasRecords does NS, CNAME, A, and AAAA lookups with a timeout -// returns error when no NS found, does not use TLDPlus1 +// returns error when no NS found, does not use alexDomain func HasRecords(domain string, timeout time.Duration) (bool, error) { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() @@ -68,10 +69,10 @@ return false, nil } -// HasRecordsCache returns true if the domain has no DNS records (at the tldplus1 level) +// HasRecordsCache returns true if the domain has no DNS records (at the apex domain level) // uses a cache to store results to prevent lots of DNS lookups func HasRecordsCache(domain string, timeout time.Duration) (bool, error) { - domain, err := TLDPlus1(domain) + domain, err := ApexDomain(domain) if err != nil { return false, err } diff --git a/dns/publicsuffix.go b/dns/publicsuffix.go index 86c9ef3..6183a4e 100644 --- a/dns/publicsuffix.go +++ b/dns/publicsuffix.go @@ -14,7 +14,6 @@ } suffixListURL = "https://publicsuffix.org/list/public_suffix_list.dat" suffixList = publicsuffix.DefaultList - nsCache = make(map[string]bool) ) // UpdatePublicSuffixList gets a new copy of the public suffix list from the internat and updates the built in copy with the new rules @@ -31,12 +30,12 @@ } defer resp.Body.Close() newSuffixList := publicsuffix.NewList() - newSuffixList.Load(resp.Body, suffixListParseOptions) + _, err = newSuffixList.Load(resp.Body, suffixListParseOptions) suffixList = newSuffixList return err } -// TLDPlus1 returns TLD+1 of domain -func TLDPlus1(domain string) (string, error) { +// ApexDomain returns TLD+1 of domain +func ApexDomain(domain string) (string, error) { return publicsuffix.DomainFromListWithOptions(suffixList, domain, suffixListFindOptions) } diff --git a/docs/index.html b/docs/index.html index c227b54..e63ac51 100644 --- a/docs/index.html +++ b/docs/index.html @@ -17,7 +17,6 @@ stroke: #333; stroke-width: 1.5px; } - .upload-drop-zone { height: 200px; border-width: 2px; diff --git a/driver/crtsh/crtsh.go b/driver/crtsh/crtsh.go index b53c038..2974102 100644 --- a/driver/crtsh/crtsh.go +++ b/driver/crtsh/crtsh.go @@ -1,13 +1,11 @@ +// Package crtsh implements an unofficial API client for Comodo's +// Certificate Transparency search +// https://crt.sh/ +// +// As the API is unofficial and has been reverse engineered it may stop working +// at any time and comes with no guarantees. +// package crtsh - -/* - * This file implements an unofficial API client for Comodo's - * Certificate Transparency search - * https://crt.sh/ - * - * As the API is unofficial and has been reverse engineered it may stop working - * at any time and comes with no guarantees. - */ // TODO running in verbose gives error: pq: unnamed prepared statement does not exist @@ -76,8 +74,11 @@ } d.db, err = sql.Open("postgres", connStr) - - d.setSQLTimeout(d.timeout.Seconds()) + if err != nil { + return nil, err + } + + err = d.setSQLTimeout(d.timeout.Seconds()) return d, err } @@ -199,7 +200,10 @@ for rows.Next() { var domain string - rows.Scan(&domain) + err = rows.Scan(&domain) + if err != nil { + return nil, err + } certNode.Domains = append(certNode.Domains, domain) } @@ -214,7 +218,10 @@ return certNode, err } - driver.RawCertToPEMFile(rawCert, path.Join(d.savePath, fp.HexString())+".pem") + err = driver.RawCertToPEMFile(rawCert, path.Join(d.savePath, fp.HexString())+".pem") + if err != nil { + return certNode, err + } } return certNode, nil diff --git a/driver/driver.go b/driver/driver.go index 5b4c819..0870aff 100644 --- a/driver/driver.go +++ b/driver/driver.go @@ -1,3 +1,4 @@ +// Package driver exposes interfaces and types certgraph drivers must implement package driver import ( @@ -8,6 +9,8 @@ "github.com/lanrat/certgraph/fingerprint" "github.com/lanrat/certgraph/status" ) + +// TODO add context instead of timeout on all requests // Drivers contains all the drivers that have been registered var Drivers []string diff --git a/driver/google/google.go b/driver/google/google.go index 321e421..eef9ae8 100644 --- a/driver/google/google.go +++ b/driver/google/google.go @@ -1,13 +1,11 @@ +// Package google file implements an unofficial API client for Google's +// Certificate Transparency search +// https://transparencyreport.google.com/https/certificates +// +// As the API is unofficial and has been reverse engineered it may stop working +// at any time and comes with no guarantees. +// package google - -/* - * This file implements an unofficial API client for Google's - * Certificate Transparency search - * https://transparencyreport.google.com/https/certificates - * - * As the API is unofficial and has been reverse engineered it may stop working - * at any time and comes with no guarantees. - */ import ( "encoding/json" @@ -30,9 +28,13 @@ } // Base URLs for Google's CT API -const searchURL1 = "https://transparencyreport.google.com/transparencyreport/api/v3/httpsreport/ct/certsearch?include_expired=false&include_subdomains=false&domain=example.com" -const searchURL2 = "https://transparencyreport.google.com/transparencyreport/api/v3/httpsreport/ct/certsearch/page?p=DEADBEEF" -const certURL = "https://transparencyreport.google.com/transparencyreport/api/v3/httpsreport/ct/certbyhash?hash=DEADBEEF" +const ( + searchURL1 = "https://transparencyreport.google.com/transparencyreport/api/v3/httpsreport/ct/certsearch?include_expired=false&include_subdomains=false&domain=example.com" + searchURL2 = "https://transparencyreport.google.com/transparencyreport/api/v3/httpsreport/ct/certsearch/page?p=DEADBEEF" + certURL = "https://transparencyreport.google.com/transparencyreport/api/v3/httpsreport/ct/certbyhash?hash=DEADBEEF" + //summaryURL is not currently used + //summaryURL = "https://transparencyreport.google.com/transparencyreport/api/v3/httpsreport/ct/summary" +) type googleCT struct { maxPages float64 // this is a float because that is the type automatically decoded from the JSON response diff --git a/driver/http/http.go b/driver/http/http.go index 591f566..376a3fc 100644 --- a/driver/http/http.go +++ b/driver/http/http.go @@ -1,3 +1,4 @@ +// Package http implements a certgraph driver for obtaining SSL certificates over https package http import ( @@ -53,7 +54,7 @@ if found { return cert, nil } - return nil, fmt.Errorf("Certificate with Fingerprint %s not found", fp.HexString()) + return nil, fmt.Errorf("certificate with Fingerprint %s not found", fp.HexString()) } // Driver creates a new SSL driver for HTTP Connections @@ -151,7 +152,7 @@ // save if c.parent.save && len(connState.PeerCertificates) > 0 { - driver.CertsToPEMFile(connState.PeerCertificates, path.Join(c.parent.savePath, certResult.Fingerprint.HexString())+".pem") + err = driver.CertsToPEMFile(connState.PeerCertificates, path.Join(c.parent.savePath, certResult.Fingerprint.HexString())+".pem") } return conn, err diff --git a/driver/save.go b/driver/save.go index e968aca..279a491 100644 --- a/driver/save.go +++ b/driver/save.go @@ -17,7 +17,10 @@ } defer f.Close() for _, cert := range certs { - pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) + err = pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) + if err != nil { + return err + } } return nil } @@ -32,8 +35,8 @@ return err } defer f.Close() - pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: cert}) - return nil + err = pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: cert}) + return err } func fileExists(f string) bool { diff --git a/driver/smtp/smtp.go b/driver/smtp/smtp.go index 3e6ed32..cb9d908 100644 --- a/driver/smtp/smtp.go +++ b/driver/smtp/smtp.go @@ -1,3 +1,4 @@ +// Package smtp implements a certgraph driver for obtaining SSL certificates over smtp with STARTTLS package smtp import ( @@ -55,7 +56,7 @@ if found { return cert, nil } - return nil, fmt.Errorf("Certificate with Fingerprint %s not found", fp.HexString()) + return nil, fmt.Errorf("certificate with Fingerprint %s not found", fp.HexString()) } // Driver creates a new SSL driver for SMTP Connections @@ -134,10 +135,10 @@ // save if d.save && len(certs) > 0 { - driver.CertsToPEMFile(certs, path.Join(d.savePath, certResult.Fingerprint.HexString())+".pem") + err = driver.CertsToPEMFile(certs, path.Join(d.savePath, certResult.Fingerprint.HexString())+".pem") } - return results, nil + return results, err } // getMX returns the MX records for the provided domain diff --git a/fingerprint/fingerprint.go b/fingerprint/fingerprint.go index 3bab154..ba96305 100644 --- a/fingerprint/fingerprint.go +++ b/fingerprint/fingerprint.go @@ -1,3 +1,4 @@ +// Package fingerprint defines types to define a certificate fingerprint for certgraph package fingerprint import ( @@ -17,9 +18,6 @@ // FromHashBytes returns a Fingerprint generated by the first len(Fingerprint) bytes func FromHashBytes(data []byte) Fingerprint { var fp Fingerprint - /*if len(data) != sha256.Size { - v("Data is not correct SHA256 size", data) - }*/ for i := 0; i < len(data) && i < len(fp); i++ { fp[i] = data[i] } @@ -28,17 +26,13 @@ // FromBytes returns a Fingerprint generated by the provided bytes func FromBytes(data []byte) Fingerprint { - var fp Fingerprint - fp = sha256.Sum256(data) + fp := sha256.Sum256(data) return fp } // FromB64 returns a Fingerprint from a base64 encoded hash string func FromB64(hash string) Fingerprint { data, _ := base64.StdEncoding.DecodeString(hash) - /*if err != nil { - fmt.Println(err) - }*/ return FromHashBytes(data) } diff --git a/go.mod b/go.mod index fff6d4e..03115bc 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,9 @@ module github.com/lanrat/certgraph require ( - github.com/lib/pq v1.0.0 - github.com/weppos/publicsuffix-go v0.4.0 - golang.org/x/net v0.0.0-20180906233101-161cd47e91fd // indirect - golang.org/x/text v0.3.0 // indirect + github.com/lib/pq v1.8.0 + github.com/weppos/publicsuffix-go v0.13.0 + golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect ) + +go 1.13 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..6f82f56 --- /dev/null +++ b/go.sum @@ -0,0 +1,14 @@ +github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg= +github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/weppos/publicsuffix-go v0.13.0 h1:0Tu1uzLBd1jPn4k6OnMmOPZH/l/9bj9kUOMMkoRs6Gg= +github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/graph/cert_node.go b/graph/cert_node.go index 13c145e..5e5ffe7 100644 --- a/graph/cert_node.go +++ b/graph/cert_node.go @@ -57,17 +57,17 @@ return false } -// TLDPlus1Count the number of tld+1 domains in the certificate -func (c *CertNode) TLDPlus1Count() int { - tldPlus1Domains := make(map[string]bool) +// ApexCount the number of tld+1 domains in the certificate +func (c *CertNode) ApexCount() int { + apexDomains := make(map[string]bool) for _, domain := range c.Domains { - tldPlus1, err := dns.TLDPlus1(domain) + apexDomain, err := dns.ApexDomain(domain) if err != nil { continue } - tldPlus1Domains[tldPlus1] = true + apexDomains[apexDomain] = true } - return len(tldPlus1Domains) + return len(apexDomains) } // ToMap returns a map of the CertNode's fields (weak serialization) diff --git a/graph/domain_node.go b/graph/domain_node.go index b18d589..e1b47b4 100644 --- a/graph/domain_node.go +++ b/graph/domain_node.go @@ -44,7 +44,7 @@ } } -// CheckForDNS checks for the existence of DNS records for the domain's tld+1 +// CheckForDNS checks for the existence of DNS records for the domain's apex // sets the value to the node and returns the result as well func (d *DomainNode) CheckForDNS(timeout time.Duration) (bool, error) { hasDNS, err := dns.HasRecordsCache(d.Domain, timeout) @@ -75,7 +75,7 @@ return fingerprints } -// get the string representation of a node +// String returns the string representation of a node func (d *DomainNode) String() string { certString := "" // Certs diff --git a/graph/graph.go b/graph/graph.go index c519040..931464c 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -1,3 +1,4 @@ +// Package graph implements the graph data structures used by certgraph to build the certificate graph package graph import ( @@ -19,13 +20,6 @@ func NewCertGraph() *CertGraph { graph := new(CertGraph) return graph -} - -// LoadOrStoreCert will return the CertNode in the graph with the provided node's fingerprint, or store the node if it did not already exist -// returned bool is true if the CertNode was found, false if stored -func (graph *CertGraph) LoadOrStoreCert(certNode *CertNode) (*CertNode, bool) { - foundCertNode, ok := graph.certs.LoadOrStore(certNode.Fingerprint, certNode) - return foundCertNode.(*CertNode), ok } // AddCert add a CertNode to the graph @@ -97,7 +91,7 @@ certNode := node.(*CertNode) if !cdn && certNode.CDNCert() { //v(domain, "-> CDN CERT") - } else if maxSANsSize > 0 && certNode.TLDPlus1Count() > maxSANsSize { + } else if maxSANsSize > 0 && certNode.ApexCount() > maxSANsSize { //v(domain, "-> Large CERT") } else { for _, neighbor := range certNode.Domains { diff --git a/status/status.go b/status/status.go index 12ec034..5e86193 100644 --- a/status/status.go +++ b/status/status.go @@ -1,3 +1,4 @@ +// Package status defines the various status certgraph discovered hosts/certificates may have package status import ( @@ -64,7 +65,7 @@ CT = iota ) -// return domain status for printing +// String returns the domain status for printing func (status DomainStatus) String() string { switch status { case UNKNOWN: diff --git a/web/generate.sh b/web/generate.sh new file mode 100755 index 0000000..f6cc5cc --- /dev/null +++ b/web/generate.sh @@ -0,0 +1,14 @@ +#!/bin/sh +set -e +HTMLFILE="../docs/index.html" +HTML="$(cat $HTMLFILE)" +DATE="$(date)" + +cat > index_html.go < + + + + + +CertGraph + + + + + +
+ + + +
+
+

Graph

+ +
+
+ +
+ +
+
Info
+
+
+
+ + +
+
+ + + + + + + + + + + + +
#DomainStatusLookup
+ +
+
+ + + + + + + + + + + +
#HashLookup
+ +
+ +
+ + + + + + + + + + + + +
+ + + + + +` + diff --git a/web/web.go b/web/web.go new file mode 100644 index 0000000..7f7c74d --- /dev/null +++ b/web/web.go @@ -0,0 +1,19 @@ +// Package web defines a minimal web server for serving the web UI +package web + +import ( + "fmt" + "net/http" +) + +//go:generate ./generate.sh + +// Serve starts a very basic webserver serving the embed web UI +func Serve(addr string) error { + http.HandleFunc("/", indexHandler) + return http.ListenAndServe(addr, nil) +} + +func indexHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "%s", indexSource) +}