Codebase list certgraph / 66fc50f
Import upstream version 20200803 Kali Janitor 3 years ago
25 changed file(s) with 794 addition(s) and 121 deletion(s). Raw diff Collapse all Expand all
0 Dockerfile
1 certgraph
2 build/
3 *.json
0 go.sum
10 certgraph
21 build/
32 *.json
0 FROM golang:alpine
1
2 RUN apk add --update git make
3
4 WORKDIR /src/certgraph
5 ADD . .
6
7 ENV CGO_ENABLED=0
8 RUN make install
9
10 ENTRYPOINT [ "certgraph" ]
00 GIT_DATE := $(shell git log -1 --date=short --pretty='%cd' | tr -d -)
11 GIT_HASH := $(shell git rev-parse HEAD)
22
3 BUILD_FLAGS := -ldflags "-X main.gitDate=$(GIT_DATE) -X main.gitHash=$(GIT_HASH)"
3 BUILD_FLAGS := -trimpath -ldflags "-w -s -X main.gitDate=$(GIT_DATE) -X main.gitHash=$(GIT_HASH)"
44
55 PLATFORMS := linux/amd64 linux/386 linux/arm darwin/amd64 windows/amd64 windows/386 openbsd/amd64
66 SOURCES := $(shell find . -maxdepth 1 -type f -name "*.go")
7 ALL_SOURCES = $(shell find . -type f -name '*.go')
7 ALL_SOURCES = $(shell find . -type f -name '*.go') go.mod web/index_html.go
88
99 temp = $(subst /, ,$@)
1010 os = $(word 1, $(temp))
1111 arch = $(word 2, $(temp))
1212 ext = $(shell if [ "$(os)" = "windows" ]; then echo ".exe"; fi)
1313
14 .PHONY: all release fmt clean serv $(PLATFORMS)
14 .PHONY: all release fmt clean serv $(PLATFORMS) docker check
1515
1616 all: certgraph
1717
2525 CGO_ENABLED=0 GOOS=$(os) GOARCH=$(arch) go build $(BUILD_FLAGS) -o 'build/bin/$(os)/$(arch)/certgraph$(ext)' $(SOURCES)
2626 mkdir -p build/$(GIT_DATE)/; cd build/bin/$(os)/$(arch)/; zip -r ../../../$(GIT_DATE)/certgraph-$(os)-$(arch)-$(GIT_DATE).zip .; cd ../../../
2727
28 web/index_html.go: docs/index.html
29 go generate -x ./...
30
31 docker: Dockerfile $(ALL_SOURCES)
32 docker build -t lanrat/certgraph .
33
2834 fmt:
2935 gofmt -s -w -l .
3036
3238 go install $(BUILD_FLAGS)
3339
3440 clean:
35 rm -r certgraph build/
41 rm -rf certgraph build/ web/index_html.go
3642
37 serv:
38 (cd docs; python -m SimpleHTTPServer)
43 check: | lint check1 check2
44
45 check1:
46 golangci-lint run
47
48 check2:
49 staticcheck -f stylish -checks all ./...
50
51 lint:
52 golint ./...
53
54 serv: certgraph
55 ./certgraph --serve 127.0.0.1:8080
56
57 updateMod:
58 go get -u
00 # CertGraph
1 ### A tool to crawl the graph of certificate Alternate Names
1
2 ## A tool to crawl the graph of certificate Alternate Names
23
34 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.
45
910 [Blog post with more information](https://lanrat.com/certgraph/)
1011
1112 ## Usage
12 ```
13
14 ```console
1315 Usage of ./certgraph: [OPTION]... HOST...
14 https://github.com/lanrat/certgraph
16 https://github.com/lanrat/certgraph
1517 OPTIONS:
18 -apex
19 for every domain found, add the apex domain of the domain's parent
1620 -cdn
17 include certificates from CDNs
21 include certificates from CDNs
1822 -ct-expired
19 include expired certificates in certificate transparency search
23 include expired certificates in certificate transparency search
2024 -ct-subdomains
21 include sub-domains in certificate transparency search
25 include sub-domains in certificate transparency search
2226 -depth uint
23 maximum BFS depth to go (default 5)
27 maximum BFS depth to go (default 5)
2428 -details
25 print details about the domains crawled
29 print details about the domains crawled
30 -dns
31 check for DNS records to determine if domain is registered
2632 -driver string
27 driver to use [crtsh, google, http, smtp] (default "http")
33 driver to use [crtsh, google, http, smtp] (default "http")
2834 -json
29 print the graph as json, can be used for graph in web UI
30 -ns
31 check for NS records to determine if domain is registered
35 print the graph as json, can be used for graph in web UI
3236 -parallel uint
33 number of certificates to retrieve in parallel (default 10)
37 number of certificates to retrieve in parallel (default 10)
3438 -sanscap int
35 maximum number of uniq TLD+1 domains in certificate to include, 0 has no limit (default 80)
39 maximum number of uniq apex domains in certificate to include, 0 has no limit (default 80)
3640 -save string
37 save certs to folder in PEM format
41 save certs to folder in PEM format
42 -serve string
43 address:port to serve html UI on
3844 -timeout uint
39 tcp timeout in seconds (default 10)
40 -tldplus1
41 for every domain found, add tldPlus1 of the domain's parent
45 tcp timeout in seconds (default 10)
46 -updatepsl
47 Update the default Public Suffix List
4248 -verbose
43 verbose logging
49 verbose logging
4450 -version
45 print version and exit
51 print version and exit
4652 ```
4753
4854 ## Drivers
4955
5056 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:
5157
52 * **http** this is the default driver which works by connecting to the hosts over HTTPS and retrieving the certificates from the SSL connection
53
54 * **smtp** like the *http* driver, but connects over port 25 and issues the *starttls* command to retrieve the certificates from the SSL connection
58 * **http** this is the default driver which works by connecting to the hosts over HTTPS and retrieving the certificates from the SSL connection
5559
56 * **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
60 * **smtp** like the *http* driver, but connects over port 25 and issues the *starttls* command to retrieve the certificates from the SSL connection
5761
58 * **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)
62 * **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
5963
64 * **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)
6065
6166 ## Example
62 ```
67
68 ```console
6369 $ ./certgraph -details eff.org
6470 eff.org 0 Good 42E3E4605D8BB4608EB64936E2176A98B97EBF2E0F8F93A64A6640713C7D4325
6571 maps.eff.org 1 Good 42E3E4605D8BB4608EB64936E2176A98B97EBF2E0F8F93A64A6640713C7D4325
6874 atlas.eff.org 1 Good 42E3E4605D8BB4608EB64936E2176A98B97EBF2E0F8F93A64A6640713C7D4325
6975 kittens.eff.org 1 Good 42E3E4605D8BB4608EB64936E2176A98B97EBF2E0F8F93A64A6640713C7D4325
7076 ```
77
7178 The above output represents the adjacency list for the graph for the root domain `eff.org`. The adjacency list is in the form:
7279 `Node Depth Status Cert-Fingerprint`
7380
7481 ## [Releases](https://github.com/lanrat/certgraph/releases)
7582
76 Precompiled releases will occasionally be uploaded to the [releases github page](https://github.com/lanrat/certgraph/releases). https://github.com/lanrat/certgraph/releases
83 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)
7784
78 Also available in [BlackArch](https://blackarch.org).
85 ### [Docker](https://hub.docker.com/r/lanrat/certgraph/)
86
87 CertGraph is an automated build on the Docker Hub!
88
89 ```console
90 $ docker run --rm -it lanrat/certgraph example.com
91 example.com
92 www.example.net
93 www.example.org
94 www.example.com
95 example.org
96 example.net
97 example.edu
98 www.example.edu
99 ```
100
101 ### Linux Distributions
102
103 * [BlackArch](https://blackarch.org)
104 * [Kali Linux](https://www.kali.org/)
79105
80106 ## Compiling
81107
82 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.
108 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.
83109 To compile for the running system compilation is as easy as running make
84 ```
110
111 ```console
85112 certgraph$ make
86113 go build -o certgraph certgraph.go
87114 ```
88115
89116 Alternatively you can use `go get` to install with this one-liner:
90 ```
117
118 ```console
91119 go get -u github.com/lanrat/certgraph
92120 ```
93121
94122 ## [Web UI](https://lanrat.github.io/certgraph/)
95123
96 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/).
124 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`.
97125
98126 The web UI takes the output provided with the `-json` flag.
99127 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.
110138
111139 [![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)
112140
141 ## BygoneSSL detection
142
143 ### Self Detection
144
145 CertGraph can be used to detect [BygoneSSL](https://insecure.design) DoS with the following options. CT-DRIVER can be any Certificate Transparency capable driver.
146 Provide all known input domains you own. If any domains you do not own are printed, then you are vulnerable.
147
148 ```console
149 certgraph -depth 1 -driver CT-DRIVER -ct-subdomains -cdn -apex [DOMAIN]...
150 ```
151
152 ### Bug Bounty
153
154 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
155
156 ```console
157 certgraph -cdn -dns -apex [DOMAIN]...
158 ```
159
160 And domains that print `* Missing DNS for` have vulnerable certificates that should be rotated.
1616 "github.com/lanrat/certgraph/driver/http"
1717 "github.com/lanrat/certgraph/driver/smtp"
1818 "github.com/lanrat/certgraph/graph"
19 "github.com/lanrat/certgraph/web"
1920 )
2021
2122 var (
2728 var certDriver driver.Driver
2829
2930 // config & flags
31 // TODO move driver options to own struct
3032 var config struct {
3133 timeout time.Duration
3234 verbose bool
4042 includeCTExpired bool
4143 cdn bool
4244 maxSANsSize int
43 tldPlus1 bool
45 apex bool
4446 updatePSL bool
4547 checkDNS bool
4648 printVersion bool
49 serve string
4750 }
4851
4952 func init() {
5457 flag.StringVar(&config.driver, "driver", "http", fmt.Sprintf("driver to use [%s]", strings.Join(driver.Drivers, ", ")))
5558 flag.BoolVar(&config.includeCTSubdomains, "ct-subdomains", false, "include sub-domains in certificate transparency search")
5659 flag.BoolVar(&config.includeCTExpired, "ct-expired", false, "include expired certificates in certificate transparency search")
57 flag.IntVar(&config.maxSANsSize, "sanscap", 80, "maximum number of uniq TLD+1 domains in certificate to include, 0 has no limit")
60 flag.IntVar(&config.maxSANsSize, "sanscap", 80, "maximum number of uniq apex domains in certificate to include, 0 has no limit")
5861 flag.BoolVar(&config.cdn, "cdn", false, "include certificates from CDNs")
5962 flag.BoolVar(&config.checkDNS, "dns", false, "check for DNS records to determine if domain is registered")
60 flag.BoolVar(&config.tldPlus1, "tldplus1", false, "for every domain found, add tldPlus1 of the domain's parent")
63 flag.BoolVar(&config.apex, "apex", false, "for every domain found, add the apex domain of the domain's parent")
6164 flag.BoolVar(&config.updatePSL, "updatepsl", false, "Update the default Public Suffix List")
6265 flag.UintVar(&config.maxDepth, "depth", 5, "maximum BFS depth to go")
6366 flag.UintVar(&config.parallel, "parallel", 10, "number of certificates to retrieve in parallel")
6467 flag.BoolVar(&config.details, "details", false, "print details about the domains crawled")
6568 flag.BoolVar(&config.printJSON, "json", false, "print the graph as json, can be used for graph in web UI")
6669 flag.StringVar(&config.savePath, "save", "", "save certs to folder in PEM format")
70 flag.StringVar(&config.serve, "serve", "", "address:port to serve html UI on")
71
6772 flag.Usage = func() {
6873 fmt.Fprintf(os.Stderr, "Usage of %s: [OPTION]... HOST...\n\thttps://github.com/lanrat/certgraph\nOPTIONS:\n", os.Args[0])
6974 flag.PrintDefaults()
7782 if config.printVersion {
7883 fmt.Println(version())
7984 return
85 }
86
87 if len(config.serve) > 0 {
88 err := web.Serve(config.serve)
89 e(err)
8090 }
8191
8292 // print usage if no domain passed
107117 d := strings.ToLower(domain)
108118 if len(d) > 0 {
109119 startDomains = append(startDomains, cleanInput(d))
110 if config.tldPlus1 {
111 tldPlus1, err := dns.TLDPlus1(domain)
120 if config.apex {
121 apexDomain, err := dns.ApexDomain(domain)
112122 if err != nil {
113123 continue
114124 }
115 startDomains = append(startDomains, tldPlus1)
125 startDomains = append(startDomains, apexDomain)
116126 }
117127 }
118128 }
147157
148158 // setDriver sets the driver variable for the provided driver string and does any necessary driver prep work
149159 // TODO make config generic and move this to driver module
160 // TODO support multi-driver
150161 func setDriver(driver string) error {
151162 var err error
152163 switch driver {
159170 case "smtp":
160171 certDriver, err = smtp.Driver(config.timeout, config.savePath)
161172 default:
162 return fmt.Errorf("Unknown driver name: %s", config.driver)
173 return fmt.Errorf("unknown driver name: %s", config.driver)
163174 }
164175 return err
165176 }
243254 for _, neighbor := range certGraph.GetDomainNeighbors(domainNode.Domain, config.cdn, config.maxSANsSize) {
244255 wg.Add(1)
245256 domainNodeInputChan <- graph.NewDomainNode(neighbor, domainNode.Depth+1)
246 if config.tldPlus1 {
247 tldPlus1, err := dns.TLDPlus1(neighbor)
257 if config.apex {
258 apexDomain, err := dns.ApexDomain(neighbor)
248259 if err != nil {
249260 continue
250261 }
251262 wg.Add(1)
252 domainNodeInputChan <- graph.NewDomainNode(tldPlus1, domainNode.Depth+1)
263 domainNodeInputChan <- graph.NewDomainNode(apexDomain, domainNode.Depth+1)
253264 }
254265 }
255266 }(domainNode)
340351 domainNode.AddCertFingerprint(certNode.Fingerprint, certDriver.GetName())
341352 }
342353
343 // we dont process any other certificates returned, they will be collected
354 // we don't process any other certificates returned, they will be collected
344355 // when we process the related domains
345356 }
346357
353364 if config.checkDNS && !domainNode.HasDNS {
354365 // TODO print this in a better way
355366 // TODO for debugging
356 realDomain, _ := dns.TLDPlus1(domainNode.Domain)
367 realDomain, _ := dns.ApexDomain(domainNode.Domain)
357368 fmt.Fprintf(os.Stdout, "* Missing DNS for: %s\n", realDomain)
358369
359370 }
0 // Package dns adds utility functions for performing dns queries
01 package dns
12
23 import (
2526 }
2627
2728 // HasRecords does NS, CNAME, A, and AAAA lookups with a timeout
28 // returns error when no NS found, does not use TLDPlus1
29 // returns error when no NS found, does not use alexDomain
2930 func HasRecords(domain string, timeout time.Duration) (bool, error) {
3031 ctx, cancel := context.WithTimeout(context.Background(), timeout)
3132 defer cancel()
6768 return false, nil
6869 }
6970
70 // HasRecordsCache returns true if the domain has no DNS records (at the tldplus1 level)
71 // HasRecordsCache returns true if the domain has no DNS records (at the apex domain level)
7172 // uses a cache to store results to prevent lots of DNS lookups
7273 func HasRecordsCache(domain string, timeout time.Duration) (bool, error) {
73 domain, err := TLDPlus1(domain)
74 domain, err := ApexDomain(domain)
7475 if err != nil {
7576 return false, err
7677 }
1313 }
1414 suffixListURL = "https://publicsuffix.org/list/public_suffix_list.dat"
1515 suffixList = publicsuffix.DefaultList
16 nsCache = make(map[string]bool)
1716 )
1817
1918 // UpdatePublicSuffixList gets a new copy of the public suffix list from the internat and updates the built in copy with the new rules
3029 }
3130 defer resp.Body.Close()
3231 newSuffixList := publicsuffix.NewList()
33 newSuffixList.Load(resp.Body, suffixListParseOptions)
32 _, err = newSuffixList.Load(resp.Body, suffixListParseOptions)
3433 suffixList = newSuffixList
3534 return err
3635 }
3736
38 // TLDPlus1 returns TLD+1 of domain
39 func TLDPlus1(domain string) (string, error) {
37 // ApexDomain returns TLD+1 of domain
38 func ApexDomain(domain string) (string, error) {
4039 return publicsuffix.DomainFromListWithOptions(suffixList, domain, suffixListFindOptions)
4140 }
1616 stroke: #333;
1717 stroke-width: 1.5px;
1818 }
19
2019 .upload-drop-zone {
2120 height: 200px;
2221 border-width: 2px;
0 // Package crtsh implements an unofficial API client for Comodo's
1 // Certificate Transparency search
2 // https://crt.sh/
3 //
4 // As the API is unofficial and has been reverse engineered it may stop working
5 // at any time and comes with no guarantees.
6 //
07 package crtsh
1
2 /*
3 * This file implements an unofficial API client for Comodo's
4 * Certificate Transparency search
5 * https://crt.sh/
6 *
7 * As the API is unofficial and has been reverse engineered it may stop working
8 * at any time and comes with no guarantees.
9 */
108
119 // TODO running in verbose gives error: pq: unnamed prepared statement does not exist
1210
7573 }
7674
7775 d.db, err = sql.Open("postgres", connStr)
78
79 d.setSQLTimeout(d.timeout.Seconds())
76 if err != nil {
77 return nil, err
78 }
79
80 err = d.setSQLTimeout(d.timeout.Seconds())
8081
8182 return d, err
8283 }
198199
199200 for rows.Next() {
200201 var domain string
201 rows.Scan(&domain)
202 err = rows.Scan(&domain)
203 if err != nil {
204 return nil, err
205 }
202206 certNode.Domains = append(certNode.Domains, domain)
203207 }
204208
213217 return certNode, err
214218 }
215219
216 driver.RawCertToPEMFile(rawCert, path.Join(d.savePath, fp.HexString())+".pem")
220 err = driver.RawCertToPEMFile(rawCert, path.Join(d.savePath, fp.HexString())+".pem")
221 if err != nil {
222 return certNode, err
223 }
217224 }
218225
219226 return certNode, nil
0 // Package driver exposes interfaces and types certgraph drivers must implement
01 package driver
12
23 import (
78 "github.com/lanrat/certgraph/fingerprint"
89 "github.com/lanrat/certgraph/status"
910 )
11
12 // TODO add context instead of timeout on all requests
1013
1114 // Drivers contains all the drivers that have been registered
1215 var Drivers []string
0 // Package google file implements an unofficial API client for Google's
1 // Certificate Transparency search
2 // https://transparencyreport.google.com/https/certificates
3 //
4 // As the API is unofficial and has been reverse engineered it may stop working
5 // at any time and comes with no guarantees.
6 //
07 package google
1
2 /*
3 * This file implements an unofficial API client for Google's
4 * Certificate Transparency search
5 * https://transparencyreport.google.com/https/certificates
6 *
7 * As the API is unofficial and has been reverse engineered it may stop working
8 * at any time and comes with no guarantees.
9 */
108
119 import (
1210 "encoding/json"
2927 }
3028
3129 // Base URLs for Google's CT API
32 const searchURL1 = "https://transparencyreport.google.com/transparencyreport/api/v3/httpsreport/ct/certsearch?include_expired=false&include_subdomains=false&domain=example.com"
33 const searchURL2 = "https://transparencyreport.google.com/transparencyreport/api/v3/httpsreport/ct/certsearch/page?p=DEADBEEF"
34 const certURL = "https://transparencyreport.google.com/transparencyreport/api/v3/httpsreport/ct/certbyhash?hash=DEADBEEF"
30 const (
31 searchURL1 = "https://transparencyreport.google.com/transparencyreport/api/v3/httpsreport/ct/certsearch?include_expired=false&include_subdomains=false&domain=example.com"
32 searchURL2 = "https://transparencyreport.google.com/transparencyreport/api/v3/httpsreport/ct/certsearch/page?p=DEADBEEF"
33 certURL = "https://transparencyreport.google.com/transparencyreport/api/v3/httpsreport/ct/certbyhash?hash=DEADBEEF"
34 //summaryURL is not currently used
35 //summaryURL = "https://transparencyreport.google.com/transparencyreport/api/v3/httpsreport/ct/summary"
36 )
3537
3638 type googleCT struct {
3739 maxPages float64 // this is a float because that is the type automatically decoded from the JSON response
0 // Package http implements a certgraph driver for obtaining SSL certificates over https
01 package http
12
23 import (
5253 if found {
5354 return cert, nil
5455 }
55 return nil, fmt.Errorf("Certificate with Fingerprint %s not found", fp.HexString())
56 return nil, fmt.Errorf("certificate with Fingerprint %s not found", fp.HexString())
5657 }
5758
5859 // Driver creates a new SSL driver for HTTP Connections
150151
151152 // save
152153 if c.parent.save && len(connState.PeerCertificates) > 0 {
153 driver.CertsToPEMFile(connState.PeerCertificates, path.Join(c.parent.savePath, certResult.Fingerprint.HexString())+".pem")
154 err = driver.CertsToPEMFile(connState.PeerCertificates, path.Join(c.parent.savePath, certResult.Fingerprint.HexString())+".pem")
154155 }
155156
156157 return conn, err
1616 }
1717 defer f.Close()
1818 for _, cert := range certs {
19 pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
19 err = pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
20 if err != nil {
21 return err
22 }
2023 }
2124 return nil
2225 }
3134 return err
3235 }
3336 defer f.Close()
34 pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: cert})
35 return nil
37 err = pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: cert})
38 return err
3639 }
3740
3841 func fileExists(f string) bool {
0 // Package smtp implements a certgraph driver for obtaining SSL certificates over smtp with STARTTLS
01 package smtp
12
23 import (
5455 if found {
5556 return cert, nil
5657 }
57 return nil, fmt.Errorf("Certificate with Fingerprint %s not found", fp.HexString())
58 return nil, fmt.Errorf("certificate with Fingerprint %s not found", fp.HexString())
5859 }
5960
6061 // Driver creates a new SSL driver for SMTP Connections
133134
134135 // save
135136 if d.save && len(certs) > 0 {
136 driver.CertsToPEMFile(certs, path.Join(d.savePath, certResult.Fingerprint.HexString())+".pem")
137 err = driver.CertsToPEMFile(certs, path.Join(d.savePath, certResult.Fingerprint.HexString())+".pem")
137138 }
138139
139 return results, nil
140 return results, err
140141 }
141142
142143 // getMX returns the MX records for the provided domain
0 // Package fingerprint defines types to define a certificate fingerprint for certgraph
01 package fingerprint
12
23 import (
1617 // FromHashBytes returns a Fingerprint generated by the first len(Fingerprint) bytes
1718 func FromHashBytes(data []byte) Fingerprint {
1819 var fp Fingerprint
19 /*if len(data) != sha256.Size {
20 v("Data is not correct SHA256 size", data)
21 }*/
2220 for i := 0; i < len(data) && i < len(fp); i++ {
2321 fp[i] = data[i]
2422 }
2725
2826 // FromBytes returns a Fingerprint generated by the provided bytes
2927 func FromBytes(data []byte) Fingerprint {
30 var fp Fingerprint
31 fp = sha256.Sum256(data)
28 fp := sha256.Sum256(data)
3229 return fp
3330 }
3431
3532 // FromB64 returns a Fingerprint from a base64 encoded hash string
3633 func FromB64(hash string) Fingerprint {
3734 data, _ := base64.StdEncoding.DecodeString(hash)
38 /*if err != nil {
39 fmt.Println(err)
40 }*/
4135 return FromHashBytes(data)
4236 }
4337
00 module github.com/lanrat/certgraph
11
22 require (
3 github.com/lib/pq v1.0.0
4 github.com/weppos/publicsuffix-go v0.4.0
5 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd // indirect
6 golang.org/x/text v0.3.0 // indirect
3 github.com/lib/pq v1.8.0
4 github.com/weppos/publicsuffix-go v0.13.0
5 golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect
76 )
7
8 go 1.13
0 github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
1 github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
2 github.com/weppos/publicsuffix-go v0.13.0 h1:0Tu1uzLBd1jPn4k6OnMmOPZH/l/9bj9kUOMMkoRs6Gg=
3 github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
4 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
5 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
6 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
7 golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
8 golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
9 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
10 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
11 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
12 golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
13 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
5656 return false
5757 }
5858
59 // TLDPlus1Count the number of tld+1 domains in the certificate
60 func (c *CertNode) TLDPlus1Count() int {
61 tldPlus1Domains := make(map[string]bool)
59 // ApexCount the number of tld+1 domains in the certificate
60 func (c *CertNode) ApexCount() int {
61 apexDomains := make(map[string]bool)
6262 for _, domain := range c.Domains {
63 tldPlus1, err := dns.TLDPlus1(domain)
63 apexDomain, err := dns.ApexDomain(domain)
6464 if err != nil {
6565 continue
6666 }
67 tldPlus1Domains[tldPlus1] = true
67 apexDomains[apexDomain] = true
6868 }
69 return len(tldPlus1Domains)
69 return len(apexDomains)
7070 }
7171
7272 // ToMap returns a map of the CertNode's fields (weak serialization)
4343 }
4444 }
4545
46 // CheckForDNS checks for the existence of DNS records for the domain's tld+1
46 // CheckForDNS checks for the existence of DNS records for the domain's apex
4747 // sets the value to the node and returns the result as well
4848 func (d *DomainNode) CheckForDNS(timeout time.Duration) (bool, error) {
4949 hasDNS, err := dns.HasRecordsCache(d.Domain, timeout)
7474 return fingerprints
7575 }
7676
77 // get the string representation of a node
77 // String returns the string representation of a node
7878 func (d *DomainNode) String() string {
7979 certString := ""
8080 // Certs
0 // Package graph implements the graph data structures used by certgraph to build the certificate graph
01 package graph
12
23 import (
1819 func NewCertGraph() *CertGraph {
1920 graph := new(CertGraph)
2021 return graph
21 }
22
23 // LoadOrStoreCert will return the CertNode in the graph with the provided node's fingerprint, or store the node if it did not already exist
24 // returned bool is true if the CertNode was found, false if stored
25 func (graph *CertGraph) LoadOrStoreCert(certNode *CertNode) (*CertNode, bool) {
26 foundCertNode, ok := graph.certs.LoadOrStore(certNode.Fingerprint, certNode)
27 return foundCertNode.(*CertNode), ok
2822 }
2923
3024 // AddCert add a CertNode to the graph
9690 certNode := node.(*CertNode)
9791 if !cdn && certNode.CDNCert() {
9892 //v(domain, "-> CDN CERT")
99 } else if maxSANsSize > 0 && certNode.TLDPlus1Count() > maxSANsSize {
93 } else if maxSANsSize > 0 && certNode.ApexCount() > maxSANsSize {
10094 //v(domain, "-> Large CERT")
10195 } else {
10296 for _, neighbor := range certNode.Domains {
0 // Package status defines the various status certgraph discovered hosts/certificates may have
01 package status
12
23 import (
6364 CT = iota
6465 )
6566
66 // return domain status for printing
67 // String returns the domain status for printing
6768 func (status DomainStatus) String() string {
6869 switch status {
6970 case UNKNOWN:
0 #!/bin/sh
1 set -e
2 HTMLFILE="../docs/index.html"
3 HTML="$(cat $HTMLFILE)"
4 DATE="$(date)"
5
6 cat > index_html.go <<EOL
7 package web
8
9 // Code generated on "$DATE" DO NOT EDIT.
10
11 const indexSource = \`$HTML\`
12
13 EOL
0 package web
1
2 // Code generated on "Mon Aug 3 14:01:58 PDT 2020" DO NOT EDIT.
3
4 const indexSource = `<!DOCTYPE html>
5 <html>
6 <head>
7 <meta charset="utf-8">
8 <meta http-equiv="X-UA-Compatible" content="IE=edge">
9 <meta name="viewport" content="width=device-width, initial-scale=1">
10 <title>CertGraph</title>
11 <link href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.7/cerulean/bootstrap.min.css" rel="stylesheet" integrity="sha384-zF4BRsG/fLiTGfR9QL82DrilZxrwgY/+du4p/c7J72zZj+FLYq4zY00RylP9ZjiT" crossorigin="anonymous">
12 <script src="https://d3js.org/d3.v4.min.js"></script>
13 <style type="text/css">
14 .links line {
15 stroke-opacity: 0.6;
16 stroke-width: 1px;
17 fill: none;
18 }
19 .nodes circle {
20 stroke: #333;
21 stroke-width: 1.5px;
22 }
23 .upload-drop-zone {
24 height: 200px;
25 border-width: 2px;
26 margin-bottom: 20px;
27 color: #ccc;
28 border-style: dashed;
29 border-color: #ccc;
30 line-height: 200px;
31 text-align: center
32 }
33 .upload-drop-zone.drop {
34 color: #222;
35 border-color: #222;
36 }
37 </style>
38 </head>
39 <body>
40 <div class="container">
41
42 <nav class="navbar navbar-inverse">
43 <div class="container-fluid">
44 <div class="navbar-header">
45 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
46 <span class="sr-only">Toggle navigation</span>
47 <span class="icon-bar"></span>
48 <span class="icon-bar"></span>
49 <span class="icon-bar"></span>
50 </button>
51 <a class="navbar-brand" href="#">CertGraph</a>
52 </div>
53 <div id="navbar" class="navbar-collapse collapse">
54 <ul class="nav navbar-nav">
55 <!-- <li><a href="#">Graph</a></li> -->
56 </ul>
57 <ul class="nav navbar-nav navbar-right">
58 <li class="dropdown">
59 <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">Data <span class="caret"></span></a>
60 <ul class="dropdown-menu" role="menu">
61 <li><a href="#" data-toggle="modal" data-target="#URLmodal">URL</a></li>
62 <li><a href="#" data-toggle="modal" data-target="#Pastemodal">Paste</a></li>
63 <li><a href="#" data-toggle="modal" data-target="#Filemodal">File Upload</a></li>
64 </ul>
65 </li>
66 </ul>
67 </div>
68 </div>
69 </nav>
70
71 <div class="panel panel-info">
72 <div class="panel-heading">
73 <h3 class="panel-title pull-left">Graph</h3>
74 <div class="pull-right"><a href="#" class="btn btn-primary btn-xs" id="generate">Download SVG</a></div>
75 <div class="clearfix"></div>
76 </div>
77 <svg id="graph" width="100%" height="500"></svg>
78 </div>
79
80 <div class="panel panel-info">
81 <div class="panel-heading">Info</div>
82 <div class="panel-body" id="node-info">
83 </div>
84 </div>
85
86 <ul class="nav nav-tabs">
87 <li class="active"><a href="#domains" data-toggle="tab" aria-expanded="false">Domains</a></li>
88 <li class=""><a href="#certificates" data-toggle="tab" aria-expanded="true">Certificates</a></li>
89 </ul>
90 <div id="myTabContent" class="tab-content panel-body">
91 <div class="tab-pane fade active in" id="domains">
92
93 <table class="table table-striped table-hover ">
94 <thead>
95 <tr>
96 <th>#</th>
97 <th>Domain</th>
98 <th>Status</th>
99 <th>Lookup</th>
100 </tr>
101 </thead>
102 <tbody id="domain-list">
103 </tbody>
104 </table>
105
106 </div>
107 <div class="tab-pane fade" id="certificates">
108
109 <table class="table table-striped table-hover ">
110 <thead>
111 <tr>
112 <th>#</th>
113 <th>Hash</th>
114 <th>Lookup</th>
115 </tr>
116 </thead>
117 <tbody id="cert-list">
118 </tbody>
119 </table>
120
121 </div>
122
123 </div>
124
125 <footer>
126 <hr>
127 <div class="row">
128 <div class="col-xs-10"><a href="https://github.com/lanrat/certgraph">CertGraph</a></div>
129 </div>
130 </footer>
131
132 <!-- URL Modal -->
133 <div class="modal fade" id="URLmodal" role="dialog">
134 <div class="modal-dialog">
135 <!-- Modal content-->
136 <div class="modal-content">
137 <div class="modal-header">
138 <button type="button" class="close" data-dismiss="modal">&times;</button>
139 <h4 class="modal-title">Data URL</h4>
140 </div>
141 <div class="modal-body">
142 <label for="inputURL" class="col-lg-2 control-label">URL</label>
143 <div class="col-lg-10">
144 <input type="text" class="form-control" id="inputURL" placeholder="https://domain.com/data.json">
145 </div>
146 </div>
147 <div class="modal-footer">
148 <button type="button" class="btn btn-primary" data-dismiss="modal" id="loadURL">Load</button>
149 <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
150 </div>
151 </div>
152 </div>
153 </div> <!-- /URL Modal -->
154
155 <!-- Paste Modal -->
156 <div class="modal fade" id="Pastemodal" role="dialog">
157 <div class="modal-dialog">
158 <!-- Modal content-->
159 <div class="modal-content">
160 <div class="modal-header">
161 <button type="button" class="close" data-dismiss="modal">&times;</button>
162 <h4 class="modal-title">JSON Data</h4>
163 </div>
164 <div class="modal-body">
165 <label for="inputPaste" class="col-lg-2 control-label">URL</label>
166 <div class="col-lg-10">
167 <textarea class="form-control" rows="10" id="inputPaste"></textarea>
168 </div>
169 </div>
170 <div class="modal-footer">
171 <button type="button" class="btn btn-primary" data-dismiss="modal" id="loadPaste">Load</button>
172 <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
173 </div>
174 </div>
175 </div>
176 </div> <!-- /Paste Modal -->
177
178 <!-- File Modal -->
179 <div class="modal fade" id="Filemodal" role="dialog">
180 <div class="modal-dialog">
181 <!-- Modal content-->
182 <div class="modal-content">
183 <div class="modal-header">
184 <button type="button" class="close" data-dismiss="modal">&times;</button>
185 <h4 class="modal-title">JSON File</h4>
186 </div>
187 <div class="modal-body">
188 <label for="inputFile" class="col-lg-2 control-label">File</label>
189 <div class="col-lg-10">
190 <input type="file" class="form-control file" id="inputFile">
191 </div>
192 <br/>
193 <label for="drop-zone" class="col-lg-2 control-label">Or drag and drop a file below</label>
194 <div class="upload-drop-zone" id="drop-zone">
195 Just drag and drop a JSON file here
196 </div>
197 </div>
198 <div class="modal-footer">
199 <button type="button" class="btn btn-primary" data-dismiss="modal" id="loadFile">Load</button>
200 <button type="button" class="btn btn-default" data-dismiss="modal" id="fileClose">Close</button>
201 </div>
202 </div>
203 </div>
204 </div> <!-- /File Modal -->
205
206 </div> <!-- /container-->
207 <script src="//code.jquery.com/jquery-1.11.3.min.js"></script>
208 <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
209 <script src="//cdn.rawgit.com/eligrey/FileSaver.js/e9d941381475b5df8b7d7691013401e171014e89/FileSaver.min.js"></script>
210 <script>
211 var svg = d3.select("svg");
212 var width = window.innerWidth-100;
213 //var width = svg.attr("width");
214 var height = svg.attr("height");
215
216 /*var svgElem = document.getElementById("graph");
217 var width = svgElem.width.animVal.value,
218 height = svgElem.height.animVal.value;
219 console.log(width, height);*/
220
221 // TODO THIS
222 // http://www.coppelia.io/2014/07/an-a-to-z-of-extra-features-for-the-d3-force-layout/
223
224 var color = d3.scaleOrdinal(d3.schemeCategory10);
225 var simulation;
226
227 svg = svg.call(d3.zoom().on("zoom", zoomed)).append("g");
228
229 svg.append("defs").append("marker")
230 .attr("id", "arrow")
231 .attr("viewBox", "0 -5 10 10")
232 .attr("refX", 20)
233 .attr("refY", 0)
234 .attr("markerWidth", 8)
235 .attr("markerHeight", 8)
236 .attr("orient", "auto")
237 //.attr("stroke", function(d) { return color(d.type); })
238 .append("svg:path")
239 .attr("d", "M0,-5L10,0L0,5");
240
241 function resetGraph() {
242 d3.select("g").selectAll("*").remove();
243 createTables();
244
245 // reset info
246 var el = document.getElementById("node-info");
247 el.innerText = "Click on a node in the graph to view details.";
248
249 // redo layout
250 simulation = d3.forceSimulation()
251 .force("link", d3.forceLink().id(function(d) { return d.id; }))
252 .force("charge", d3.forceManyBody().strength(-100))
253 .force("center", d3.forceCenter(width / 2, height / 2));
254 }
255
256 function createGraph (error, graph) {
257 if (error) throw error;
258
259 var link = svg.append("g")
260 .attr("class", "links")
261 .selectAll("line")
262 .data(graph.links)
263 .enter().append("line")
264 .attr("stroke", function(d) { return color(d.type); })
265 .attr("marker-end", "url(#arrow)");
266
267 var text = svg.append("g").attr("class", "labels").selectAll("g")
268 .data(graph.nodes)
269 .enter().append("g");
270
271 text.append("text")
272 .attr("x", 14)
273 .attr("y", ".31em")
274 .style("font-family", "sans-serif")
275 .style("font-size", "0.7em")
276 .text(function(d) { if (d.type == "domain") {return d.id; } return d.id.substring(0,8); });
277
278 var node = svg.append("g")
279 .attr("class", "nodes")
280 .selectAll("circle")
281 .data(graph.nodes)
282 .enter().append("circle")
283 .attr("r", 10)
284 .attr("fill", function(d) { if (d.root == "true") return color(d.root); return color(d.type); })
285 .call(d3.drag()
286 .on("start", dragstarted)
287 .on("drag", dragged)
288 .on("end", dragended));
289
290 node.on("click",function(d){
291 // console.log("clicked", d.id);
292 // console.log(d);
293 updateInfoBox(d);
294 });
295
296 node.append("title")
297 .text(function(d) { return d.id; });
298
299 simulation
300 .nodes(graph.nodes)
301 .on("tick", ticked);
302
303 simulation.force("link")
304 .links(graph.links);
305
306 function ticked() {
307 link
308 .attr("x1", function(d) { return d.source.x; })
309 .attr("y1", function(d) { return d.source.y; })
310 .attr("x2", function(d) { return d.target.x; })
311 .attr("y2", function(d) { return d.target.y; });
312
313 node
314 .attr("cx", function(d) { return d.x; })
315 .attr("cy", function(d) { return d.y; });
316 text
317 .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"});
318 }
319 createTables();
320 }
321
322
323 function dragstarted(d) {
324 if (!d3.event.active) simulation.alphaTarget(0.3).restart();
325 d.fx = d.x;
326 d.fy = d.y;
327 }
328
329 function dragged(d) {
330 d.fx = d3.event.x;
331 d.fy = d3.event.y;
332 }
333
334 function dragended(d) {
335 if (!d3.event.active) simulation.alphaTarget(0);
336 d.fx = null;
337 d.fy = null;
338 }
339
340 function zoomed() {
341 svg.attr("transform", "translate(" + d3.event.transform.x + "," + d3.event.transform.y + ")" + " scale(" + d3.event.transform.k + ")");
342 }
343
344 d3.select("#generate").on("click", writeDownloadLink);
345 function writeDownloadLink(){
346 try {
347 var isFileSaverSupported = !!new Blob();
348 } catch (e) {
349 alert("blob not supported");
350 }
351
352 var html = d3.select("svg")
353 .attr("title", "graph") //TODO
354 .attr("version", 1.1)
355 .attr("xmlns", "http://www.w3.org/2000/svg")
356 .node().outerHTML;
357
358 var blob = new Blob([html], {type: "image/svg+xml"});
359 saveAs(blob, "certificate_graph.svg"); //TODO root node name
360 };
361
362 function updateInfoBox(d) {
363 if (d) {
364 var el = document.getElementById("node-info");
365 var s = "Type: "+d.type+"</br>";
366 if (d.type == "domain") {
367 s = s + "Domain: "+linkifyDomain(d)+"</br>";
368 s = s + "Status: "+d.status+"</br>";
369 }else if (d.type = "certificate") {
370 s = s + "Hash: "+linkifyCert(d)+"</br>";
371 }
372 el.innerHTML = s;
373 }
374 }
375
376 function createTables() {
377 // TODO: redo this in native d3
378 domainEl = document.getElementById("domain-list");
379 domain_tbody2 = document.createElement('tbody');
380 domain_tbody2.id="domain-list";
381 domainEl.parentNode.replaceChild(domain_tbody2, domainEl);
382
383 certEl = document.getElementById("cert-list");
384 cert_tbody2 = document.createElement('tbody');
385 cert_tbody2.id="cert-list";
386 certEl.parentNode.replaceChild(cert_tbody2, certEl);
387
388 var domainCount = 0;
389 function addTableDomain(d) {
390 //console.log("domain", d);
391 var c = "";
392 if (d.root == "true") {
393 c = "info";
394 }
395 $('#domain-list').append('<tr class="'+c+'"><td>'+ ++domainCount +'</td><td>'+linkifyDomain(d)+'</td><td>'+d.status+'</td><td>'+linkifyAny(d)+'</td></tr>');
396 }
397
398 var certCount = 0;
399 function addTableCert(d) {
400 //console.log("cert", d);
401 $('#cert-list').append('<tr><td>'+ ++certCount + '</td><td>'+linkifyCert(d)+'</td><td>'+linkifyAny(d)+'</td></tr>');
402 }
403
404
405 d3.selectAll('circle').each(function(d){
406 if (d.type == "domain") {
407 addTableDomain(d);
408 }else if (d.type == "certificate") {
409 addTableCert(d);
410 } else {
411 console.log("Unknown Type: ", d.type);
412 }
413 })
414 }
415
416 function linkifyCert(d) {
417 return '<a target="_blank" href="https://crt.sh/?sha256='+d.id+'">'+d.id+'</a>';
418 }
419 function linkifyDomain(d) {
420 return '<a target="_blank" href="https://'+d.id+'">'+d.id+'</a>';
421 }
422 function linkifyAny(d) {
423 return '<a target="_blank" href="https://crt.sh/?q='+d.id+'">&#x1F50E;</a>';
424 }
425
426 function getQueryVariable(variable){
427 var query = window.location.search.substring(1);
428 var vars = query.split("&");
429 for (var i=0;i<vars.length;i++) {
430 var pair = vars[i].split("=");
431 if(pair[0] == variable){return pair[1];}
432 }
433 return "";
434 }
435
436 // new data from url
437 d3.select("#loadURL").on("click", loadURL);
438 function loadURL(){
439 var url = document.getElementById("inputURL").value;
440 history.pushState('', 'CertGraph', "?data="+url);
441 resetGraph();
442 d3.json(url, createGraph);
443 }
444
445 // new data from paste
446 d3.select("#loadPaste").on("click", loadPaste);
447 function loadPaste(){
448 var dataStr = document.getElementById("inputPaste").value;
449 history.pushState('', 'CertGraph', "?");
450 resetGraph();
451 var data = JSON.parse(dataStr);
452 createGraph(null, data);
453 }
454
455 // new data from paste
456 d3.select("#loadFile").on("click", loadFile);
457 function loadFile(){
458 var file = document.getElementById("inputFile").files[0];
459 history.pushState('', 'CertGraph', "?");
460 resetGraph();
461 var reader = new FileReader();
462 reader.onload = function(e) {
463 var dataStr = reader.result;
464 var data = JSON.parse(dataStr);
465 createGraph(null, data);
466 }
467 reader.readAsText(file);
468 }
469
470 var dropbox = document.getElementById('drop-zone');
471 function dragenter(e) {
472 e.stopPropagation();
473 e.preventDefault();
474 dropbox.className = 'upload-drop-zone drop';
475 // console.log("enter");
476 return false;
477 }
478 function dragover(e) {
479 e.stopPropagation();
480 e.preventDefault();
481 // console.log("over");
482 }
483 function dragleave(e) {
484 e.stopPropagation();
485 e.preventDefault();
486 dropbox.className = 'upload-drop-zone';
487 // console.log("leave");
488 return false;
489 }
490 function drop(e) {
491 // console.log("drop");
492 e.stopPropagation();
493 e.preventDefault();
494 dropbox.className = 'upload-drop-zone';
495
496 var dt = e.dataTransfer;
497 var files = dt.files;
498
499 var reader = new FileReader();
500 reader.onload = function(e) {
501 var dataStr = reader.result;
502 var data = JSON.parse(dataStr);
503 resetGraph();
504 createGraph(null, data);
505 }
506 reader.readAsText(files[0]);
507 $('#fileClose').click();
508 return false;
509 }
510 dropbox.addEventListener("dragenter", dragenter, false);
511 dropbox.addEventListener("dragover", dragover, false);
512 dropbox.addEventListener("drop", drop, false);
513 dropbox.addEventListener("dragleave",dragleave, false);
514
515 // load initial graph data
516 var dataURL = getQueryVariable("data");
517 if (dataURL == "") {
518 // default graph
519 dataURL = "https://gist.githubusercontent.com/lanrat/8187d01793bf3e578d76495182654206/raw/c49741b5206d81935febdf563452cc4346381e52/eff.json";
520 }
521 resetGraph();
522 d3.json(dataURL, createGraph);
523 </script>
524 </body>
525 </html>`
526
0 // Package web defines a minimal web server for serving the web UI
1 package web
2
3 import (
4 "fmt"
5 "net/http"
6 )
7
8 //go:generate ./generate.sh
9
10 // Serve starts a very basic webserver serving the embed web UI
11 func Serve(addr string) error {
12 http.HandleFunc("/", indexHandler)
13 return http.ListenAndServe(addr, nil)
14 }
15
16 func indexHandler(w http.ResponseWriter, r *http.Request) {
17 fmt.Fprintf(w, "%s", indexSource)
18 }