Skip to content

Commit 021c704

Browse files
committed
added censys driver
1 parent 20aaf14 commit 021c704

File tree

9 files changed

+584
-12
lines changed

9 files changed

+584
-12
lines changed

Makefile

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ os = $(word 1, $(temp))
1212
arch = $(word 2, $(temp))
1313
ext = $(shell if [ "$(os)" = "windows" ]; then echo ".exe"; fi)
1414

15-
.PHONY: all release fmt clean serv $(PLATFORMS) docker check deps
15+
.PHONY: all release fmt clean serv $(PLATFORMS) docker check deps update-deps
1616

1717
all: certgraph
1818

@@ -32,7 +32,6 @@ docker: Dockerfile $(ALL_SOURCES)
3232
deps: go.mod
3333
GOPROXY=direct go mod download
3434
GOPROXY=direct go get -u all
35-
go mod tidy
3635

3736
fmt:
3837
gofmt -s -w -l .
@@ -57,5 +56,9 @@ lint:
5756
serv: certgraph
5857
./certgraph --serve 127.0.0.1:8080
5958

60-
updateMod:
59+
update-deps:
6160
go get -u
61+
go mod tidy
62+
63+
test:
64+
go test -v ./... | grep -v "\[no test files\]"

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ OPTIONS:
3131
-dns
3232
check for DNS records to determine if domain is registered
3333
-driver string
34-
driver(s) to use [crtsh, google, http, smtp] (default "http")
34+
driver(s) to use [censys, crtsh, google, http, smtp] (default "http")
3535
-json
3636
print the graph as json, can be used for graph in web UI
3737
-parallel uint
@@ -62,6 +62,8 @@ CertGraph has multiple options for querying SSL certificates. The driver is resp
6262

6363
* **smtp** like the *http* driver, but connects over port 25 and issues the *starttls* command to retrieve the certificates from the SSL connection
6464

65+
* **censys** this driver searches Certificate Transparency logs via [censys.io](https://search.censys.io/certificates). No packets are sent to any of the domains when using this driver. Requires Censys API keys
66+
6567
* **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
6668

6769
* **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)

certgraph.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"github.com/lanrat/certgraph/dns"
1616
"github.com/lanrat/certgraph/driver"
17+
"github.com/lanrat/certgraph/driver/censys"
1718
"github.com/lanrat/certgraph/driver/crtsh"
1819
"github.com/lanrat/certgraph/driver/google"
1920
"github.com/lanrat/certgraph/driver/http"
@@ -213,6 +214,8 @@ func getDriverSingle(name string) (driver.Driver, error) {
213214
d, err = http.Driver(config.timeout, config.savePath)
214215
case "smtp":
215216
d, err = smtp.Driver(config.timeout, config.savePath)
217+
case "censys":
218+
d, err = censys.Driver(config.savePath, config.includeCTSubdomains, config.includeCTExpired)
216219
default:
217220
return nil, fmt.Errorf("unknown driver name: %s", config.driver)
218221
}

driver/censys/censys.go

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
package censys
2+
3+
import (
4+
"bytes"
5+
"encoding/base64"
6+
"encoding/json"
7+
"flag"
8+
"fmt"
9+
"io"
10+
"log"
11+
"net/http"
12+
"path"
13+
"time"
14+
15+
"github.com/lanrat/certgraph/driver"
16+
"github.com/lanrat/certgraph/fingerprint"
17+
"github.com/lanrat/certgraph/status"
18+
)
19+
20+
const driverName = "censys"
21+
22+
var debug = false
23+
24+
// TODO support rate limits & pagnation
25+
26+
var (
27+
defaultHTTPClient = &http.Client{}
28+
29+
appID = flag.String("censys-appid", "", "censys API AppID")
30+
secret = flag.String("censys-secret", "", "censys API Secret")
31+
)
32+
33+
func init() {
34+
driver.AddDriver(driverName)
35+
}
36+
37+
type censys struct {
38+
appID string
39+
secret string
40+
save bool
41+
savePath string
42+
includeSubdomains bool
43+
includeExpired bool
44+
}
45+
46+
type censysCertDriver struct {
47+
host string
48+
fingerprints driver.FingerprintMap
49+
driver *censys
50+
}
51+
52+
func (c *censysCertDriver) GetFingerprints() (driver.FingerprintMap, error) {
53+
return c.fingerprints, nil
54+
}
55+
56+
func (c *censysCertDriver) GetStatus() status.Map {
57+
return status.NewMap(c.host, status.New(status.CT))
58+
}
59+
60+
func (c *censysCertDriver) GetRelated() ([]string, error) {
61+
return make([]string, 0), nil
62+
}
63+
64+
func (c *censysCertDriver) QueryCert(fp fingerprint.Fingerprint) (*driver.CertResult, error) {
65+
return c.driver.QueryCert(fp)
66+
}
67+
68+
// TODO support pagnation
69+
func domainSearchParam(domain string, includeExpired, includeSubdomain bool) certSearchParam {
70+
var s certSearchParam
71+
if includeSubdomain {
72+
s.Query = fmt.Sprintf("(parsed.names: %s )", domain)
73+
} else {
74+
s.Query = fmt.Sprintf("(parsed.names.raw: %s)", domain)
75+
}
76+
if !includeExpired {
77+
dateStr := time.Now().Format("2006-01-02") // YYYY-MM-DD
78+
expQuery := fmt.Sprintf(" AND ((parsed.validity.end: [%s TO *]) AND (parsed.validity.start: [* TO %s]))", dateStr, dateStr)
79+
s.Query = s.Query + expQuery
80+
}
81+
s.Page = 1
82+
s.Flatten = true
83+
s.Fields = []string{"parsed.fingerprint_sha256", "parsed.names"}
84+
return s
85+
}
86+
87+
// Driver creates a new CT driver for censys
88+
func Driver(savePath string, includeSubdomains, includeExpired bool) (driver.Driver, error) {
89+
if *appID == "" || *secret == "" {
90+
return nil, fmt.Errorf("censys requires an appID and secret to run")
91+
}
92+
d := new(censys)
93+
d.appID = *appID
94+
d.secret = *secret
95+
d.savePath = savePath
96+
d.includeSubdomains = includeSubdomains
97+
d.includeExpired = includeExpired
98+
return d, nil
99+
}
100+
101+
func (d *censys) GetName() string {
102+
return driverName
103+
}
104+
105+
func (d *censys) request(method, url string, request io.Reader) (*http.Response, error) {
106+
totalTrys := 3
107+
var err error
108+
var req *http.Request
109+
var resp *http.Response
110+
for try := 1; try <= totalTrys; try++ {
111+
req, err = http.NewRequest(method, url, request)
112+
if err != nil {
113+
return nil, err
114+
}
115+
if request != nil {
116+
req.Header.Add("Content-Type", "application/json")
117+
}
118+
req.Header.Add("Accept", "application/json")
119+
req.SetBasicAuth(d.appID, d.secret)
120+
121+
resp, err = defaultHTTPClient.Do(req)
122+
if err != nil {
123+
err = fmt.Errorf("error on request [%d/%d] %s, got error %w: %+v", try, totalTrys, url, err, resp)
124+
} else {
125+
return resp, nil
126+
}
127+
128+
// sleep only if we will try again
129+
if try < totalTrys {
130+
time.Sleep(time.Second * 10)
131+
}
132+
}
133+
return resp, err
134+
}
135+
136+
// jsonRequest performes a request to the API endpoint sending and receiving JSON objects
137+
func (d *censys) jsonRequest(method, url string, request, response interface{}) error {
138+
var payloadReader io.Reader
139+
if request != nil {
140+
jsonPayload, err := json.Marshal(request)
141+
if err != nil {
142+
return err
143+
}
144+
payloadReader = bytes.NewReader(jsonPayload)
145+
}
146+
147+
if debug {
148+
log.Printf("DEBUG: request to %s %s", method, url)
149+
if request != nil {
150+
prettyJSONBytes, _ := json.MarshalIndent(request, "", "\t")
151+
log.Printf("request payload:\n%s\n", string(prettyJSONBytes))
152+
}
153+
}
154+
155+
resp, err := d.request(method, url, payloadReader)
156+
if err != nil {
157+
return err
158+
}
159+
defer resp.Body.Close()
160+
161+
// got an error, decode it
162+
if resp.StatusCode != http.StatusOK {
163+
var errorResp errorResponse
164+
err := fmt.Errorf("error on request %s, got Status %s %s", url, resp.Status, http.StatusText(resp.StatusCode))
165+
jsonError := json.NewDecoder(resp.Body).Decode(&errorResp)
166+
if jsonError != nil {
167+
return fmt.Errorf("error decoding json %w on errord request: %s", jsonError, err.Error())
168+
}
169+
return fmt.Errorf("%w, HTTPStatus: %d Message: %q", err, errorResp.ErrorCode, errorResp.Error)
170+
}
171+
172+
if response != nil {
173+
err = json.NewDecoder(resp.Body).Decode(&response)
174+
if err != nil {
175+
return err
176+
}
177+
if debug {
178+
prettyJSONBytes, _ := json.MarshalIndent(response, "", "\t")
179+
log.Printf("response payload:\n%s\n", string(prettyJSONBytes))
180+
}
181+
}
182+
183+
return nil
184+
}
185+
186+
func (d *censys) QueryDomain(domain string) (driver.Result, error) {
187+
results := &censysCertDriver{
188+
host: domain,
189+
fingerprints: make(driver.FingerprintMap),
190+
driver: d,
191+
}
192+
params := domainSearchParam(domain, d.includeExpired, d.includeSubdomains)
193+
url := "https://search.censys.io/api/v1/search/certificates"
194+
var resp certSearchResponse
195+
err := d.jsonRequest(http.MethodPost, url, params, &resp)
196+
if err != nil {
197+
return results, err
198+
}
199+
200+
for _, r := range resp.Results {
201+
fp := fingerprint.FromHexHash(r.Fingerprint)
202+
results.fingerprints.Add(domain, fp)
203+
}
204+
205+
if debug {
206+
log.Printf("censys: got %d results for %s.", len(resp.Results), domain)
207+
}
208+
209+
return results, nil
210+
}
211+
212+
func (d *censys) QueryCert(fp fingerprint.Fingerprint) (*driver.CertResult, error) {
213+
certNode := new(driver.CertResult)
214+
certNode.Fingerprint = fp
215+
certNode.Domains = make([]string, 0, 5)
216+
217+
url := fmt.Sprintf("https://search.censys.io/api/v1/view/certificates/%s", fp.HexString())
218+
var resp certViewResponse
219+
err := d.jsonRequest(http.MethodGet, url, nil, &resp)
220+
if err != nil {
221+
return certNode, err
222+
}
223+
224+
if debug {
225+
log.Printf("DEBUG QueryCert(%s): %v", fp.HexString(), resp.Parsed.Names)
226+
}
227+
228+
certNode.Domains = append(certNode.Domains, resp.Parsed.Names...)
229+
230+
if d.save {
231+
rawCert, err := base64.StdEncoding.DecodeString(resp.Raw)
232+
if err != nil {
233+
return certNode, err
234+
}
235+
err = driver.RawCertToPEMFile(rawCert, path.Join(d.savePath, fp.HexString())+".pem")
236+
if err != nil {
237+
return certNode, err
238+
}
239+
}
240+
241+
return certNode, nil
242+
}

0 commit comments

Comments
 (0)