Skip to content

Commit f3c062d

Browse files
committed
Added crt.sh CT driver
1 parent c62ef5e commit f3c062d

11 files changed

Lines changed: 170 additions & 58 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
Gopkg.lock
12
certgraph
23
build/
34
*.json

Gopkg.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
[[constraint]]
3+
branch = "master"
4+
name = "github.com/lib/pq"

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ $(PLATFORMS): $(SOURCES)
2323
CGO_ENABLED=0 GOOS=$(os) GOARCH=$(arch) go build $(BUILD_FLAGS) -o 'build/$(os)/$(arch)/certgraph$(ext)' $(SOURCES)
2424
cd build/$(os)/$(arch)/; zip -r ../../certgraph-$(os)-$(arch)-$(GIT_DATE).zip .; cd ../../../
2525

26+
dep:
27+
dep ensure
28+
2629
fmt:
2730
gofmt -s -w -l .
2831

certgraph.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ import (
1616
"sync"
1717
"time"
1818

19-
"github.com/lanrat/certgraph/ct/google"
19+
"github.com/lanrat/certgraph/driver"
20+
"github.com/lanrat/certgraph/driver/crtsh"
21+
"github.com/lanrat/certgraph/driver/google"
2022
"github.com/lanrat/certgraph/graph"
2123
"github.com/lanrat/certgraph/status"
2224
)
@@ -49,6 +51,8 @@ var include_ct_sub bool
4951
var tls_connect bool
5052
var ver bool
5153
var skipCDN bool
54+
var crtsh_driver bool
55+
var ctDriver driver.Driver
5256

5357
func generateGraphMetadata() map[string]interface{} {
5458
data := make(map[string]interface{})
@@ -81,6 +85,7 @@ func main() {
8185
timeoutPtr := flag.Uint("timeout", 5, "tcp timeout in seconds")
8286
flag.BoolVar(&verbose, "verbose", false, "verbose logging")
8387
flag.BoolVar(&ct, "ct", false, "use certificate transparancy search to find certificates")
88+
flag.BoolVar(&crtsh_driver, "crtsh", false, "use the CRT.sh api instead of Google for CT")
8489
flag.BoolVar(&include_ct_sub, "ct-subdomains", false, "include sub-domains in certificate transparancy search")
8590
flag.BoolVar(&skipCDN, "skip-cdn", false, "do not crawl into CDN certs")
8691
flag.BoolVar(&notls, "notls", false, "don't connect to hosts to collect certificates")
@@ -117,6 +122,20 @@ func main() {
117122
return
118123
}
119124

125+
// TODO better driver support
126+
if ct {
127+
var err error
128+
if crtsh_driver {
129+
ctDriver, err = crtsh.NewCRTshDriver()
130+
} else {
131+
ctDriver, err = google.NewGoogleCTDriver()
132+
}
133+
if err != nil {
134+
fmt.Fprintln(os.Stderr, err)
135+
return
136+
}
137+
}
138+
120139
// set verbose loggin
121140
graph.Verbose = verbose
122141

@@ -268,7 +287,7 @@ func BFSVisit(node *graph.DomainNode) {
268287
func visitCT(node *graph.DomainNode) {
269288
// perform ct search
270289
// TODO do pagnation in multiple threads to not block on long searches
271-
fingerprints, err := google.QueryDomain(node.Domain, false, include_ct_sub)
290+
fingerprints, err := ctDriver.QueryDomain(node.Domain, false, include_ct_sub)
272291
if err != nil {
273292
v(err)
274293
return
@@ -282,7 +301,7 @@ func visitCT(node *graph.DomainNode) {
282301

283302
if !exists {
284303
// get cert details
285-
certnode, err = google.GetCert(fp)
304+
certnode, err = ctDriver.QueryCert(fp)
286305
if err != nil {
287306
v(err)
288307
continue

ct/.gitignore

Lines changed: 0 additions & 1 deletion
This file was deleted.

ct/Makefile

Lines changed: 0 additions & 15 deletions
This file was deleted.

ct/main.go

Lines changed: 0 additions & 19 deletions
This file was deleted.

driver/crtsh/crtsh.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package crtsh
2+
3+
import (
4+
"database/sql"
5+
"fmt"
6+
"github.com/lanrat/certgraph/driver"
7+
"github.com/lanrat/certgraph/graph"
8+
_ "github.com/lib/pq"
9+
)
10+
11+
const connStr = "postgresql://guest@crt.sh/certwatch?sslmode=disable"
12+
13+
type crtsh struct {
14+
db *sql.DB
15+
}
16+
17+
func NewCRTshDriver() (driver.Driver, error) {
18+
d := new(crtsh)
19+
var err error
20+
21+
d.db, err = sql.Open("postgres", connStr)
22+
23+
return d, err
24+
}
25+
26+
func (d *crtsh) QueryDomain(domain string, include_expired bool, include_subdomains bool) ([]graph.Fingerprint, error) {
27+
results := make([]graph.Fingerprint, 0, 5)
28+
29+
queryStr := "SELECT digest(certificate.certificate, 'sha256') sha256 FROM certificate_identity, certificate WHERE certificate.id = certificate_identity.certificate_id AND x509_notAfter(certificate.certificate) > statement_timestamp() AND reverse(lower(certificate_identity.name_value)) LIKE reverse(lower($1))"
30+
if include_expired {
31+
queryStr = "SELECT digest(certificate.certificate, 'sha256') sha256 FROM certificate_identity, certificate WHERE certificate.id = certificate_identity.certificate_id AND reverse(lower(certificate_identity.name_value)) LIKE reverse(lower($1))"
32+
}
33+
34+
if include_subdomains {
35+
domain = fmt.Sprintf("%%.%s", domain)
36+
}
37+
38+
rows, err := d.db.Query(queryStr, domain)
39+
if err != nil {
40+
return results, err
41+
}
42+
43+
for rows.Next() {
44+
var hash []byte
45+
err = rows.Scan(&hash)
46+
if err != nil {
47+
return results, err
48+
}
49+
results = append(results, graph.FingerprintFromBytes(hash))
50+
}
51+
52+
return results, nil
53+
}
54+
55+
func (d *crtsh) QueryCert(fp graph.Fingerprint) (*graph.CertNode, error) {
56+
certnode := new(graph.CertNode)
57+
certnode.Fingerprint = fp
58+
certnode.Domains = make([]string, 0, 5)
59+
certnode.CT = true
60+
61+
queryStr := "SELECT DISTINCT certificate_identity.name_value FROM certificate, certificate_identity WHERE certificate.id = certificate_identity.certificate_id AND digest(certificate.certificate, 'sha256') = $1"
62+
63+
rows, err := d.db.Query(queryStr, fp[:])
64+
if err != nil {
65+
return certnode, err
66+
}
67+
68+
for rows.Next() {
69+
var domain string
70+
rows.Scan(&domain)
71+
certnode.Domains = append(certnode.Domains, domain)
72+
}
73+
74+
return certnode, nil
75+
}
76+
77+
func (d *crtsh) CTexample(domain string) error {
78+
s, err := d.QueryDomain(domain, false, false)
79+
if err != nil {
80+
return err
81+
}
82+
83+
for i := range s {
84+
fmt.Println(s[i].HexString(), " ", s[i].B64Encode())
85+
cert, err := d.QueryCert(s[i])
86+
if err != nil {
87+
return err
88+
}
89+
for j := range cert.Domains {
90+
fmt.Println("\t", cert.Domains[j])
91+
}
92+
}
93+
94+
return nil
95+
}

driver/driver.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package driver
2+
3+
import (
4+
"github.com/lanrat/certgraph/graph"
5+
)
6+
7+
type Driver interface {
8+
QueryDomain(domain string, include_expired bool, include_subdomains bool) ([]graph.Fingerprint, error)
9+
QueryCert(fp graph.Fingerprint) (*graph.CertNode, error)
10+
CTexample(domain string) error
11+
}
Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package google
33
/*
44
* This file implements an unofficial API client for Google's
55
* Certificate Transparency search
6-
* https://www.google.com/transparencyreport/https/ct/
6+
* https://transparencyreport.google.com/https/certificates
77
*
88
* As the API is unofficial and has been reverse engineered it may stop working
99
* at any time and comes with no guarantees.
@@ -19,21 +19,31 @@ import (
1919
"strconv"
2020
"time"
2121

22+
"github.com/lanrat/certgraph/driver"
2223
"github.com/lanrat/certgraph/graph"
2324
)
2425

2526
// BASE URLs for Googl'e CT API
2627
const searchURL1 = "https://transparencyreport.google.com/transparencyreport/api/v3/httpsreport/ct/certsearch?include_expired=false&include_subdomains=false&domain=example.com"
2728
const searchURL2 = "https://transparencyreport.google.com/transparencyreport/api/v3/httpsreport/ct/certsearch/page?p=DEADBEEF"
2829
const certURL = "https://transparencyreport.google.com/transparencyreport/api/v3/httpsreport/ct/certbyhash?hash=DEADBEEF"
29-
const MAX_PAGES = 50 // TODO: something better than hard-coding
3030

31-
// global vars
32-
var jsonClient = &http.Client{Timeout: 10 * time.Second}
31+
type googleCT struct {
32+
max_pages float64
33+
jsonClient *http.Client
34+
}
35+
36+
func NewGoogleCTDriver() (driver.Driver, error) {
37+
d := new(googleCT)
38+
d.max_pages = 50 // TODO: make adjustable
39+
d.jsonClient = &http.Client{Timeout: 10 * time.Second}
40+
41+
return d, nil
42+
}
3343

3444
// gets JSON from url and parses it into target object
35-
func getJsonP(url string, target interface{}) error {
36-
r, err := jsonClient.Get(url)
45+
func (d *googleCT) getJsonP(url string, target interface{}) error {
46+
r, err := d.jsonClient.Get(url)
3747
if err != nil {
3848
return err
3949
}
@@ -52,7 +62,7 @@ func getJsonP(url string, target interface{}) error {
5262
return json.Unmarshal(respData, target)
5363
}
5464

55-
func QueryDomain(domain string, include_expired bool, include_subdomains bool) ([]graph.Fingerprint, error) {
65+
func (d *googleCT) QueryDomain(domain string, include_expired bool, include_subdomains bool) ([]graph.Fingerprint, error) {
5666
results := make([]graph.Fingerprint, 0, 5)
5767

5868
u, err := url.Parse(searchURL1)
@@ -74,8 +84,8 @@ func QueryDomain(domain string, include_expired bool, include_subdomains bool) (
7484
// TODO allow for selective pagnation
7585

7686
// iterate over results
77-
for len(nextURL) > 1 && currentPage <= MAX_PAGES {
78-
err = getJsonP(nextURL, &raw)
87+
for len(nextURL) > 1 && currentPage <= d.max_pages {
88+
err = d.getJsonP(nextURL, &raw)
7989
if err != nil {
8090
return results, err
8191
}
@@ -127,7 +137,7 @@ func QueryDomain(domain string, include_expired bool, include_subdomains bool) (
127137
return results, nil
128138
}
129139

130-
func GetCert(fp graph.Fingerprint) (*graph.CertNode, error) {
140+
func (d *googleCT) QueryCert(fp graph.Fingerprint) (*graph.CertNode, error) {
131141
certnode := new(graph.CertNode)
132142
certnode.Fingerprint = fp
133143
certnode.Domains = make([]string, 0, 5)
@@ -144,7 +154,7 @@ func GetCert(fp graph.Fingerprint) (*graph.CertNode, error) {
144154

145155
var raw [][]interface{}
146156

147-
err = getJsonP(u.String(), &raw)
157+
err = d.getJsonP(u.String(), &raw)
148158
if err != nil {
149159
return certnode, err
150160
}
@@ -170,15 +180,15 @@ func GetCert(fp graph.Fingerprint) (*graph.CertNode, error) {
170180
}
171181

172182
// example function to use Google's CT API
173-
func CTexample(domain string) error {
174-
s, err := QueryDomain(domain, false, false)
183+
func (d *googleCT) CTexample(domain string) error {
184+
s, err := d.QueryDomain(domain, false, false)
175185
if err != nil {
176186
return err
177187
}
178188

179189
for i := range s {
180190
fmt.Println(s[i].HexString(), " ", s[i].B64Encode())
181-
cert, err := GetCert(s[i])
191+
cert, err := d.QueryCert(s[i])
182192
if err != nil {
183193
return err
184194
}

0 commit comments

Comments
 (0)