-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 17ca78a
Showing
11 changed files
with
501 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# This workflow will build a golang project | ||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go | ||
|
||
name: Go Build Cert Checker | ||
|
||
on: | ||
push: | ||
branches: [ "main" ] | ||
pull_request: | ||
branches: [ "main" ] | ||
|
||
jobs: | ||
|
||
build: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
|
||
- name: Set up Go | ||
uses: actions/setup-go@v4 | ||
with: | ||
go-version: '1.22' | ||
|
||
- name: Build | ||
run: go build -ldflags "-s -w -X main.sha1ver=`git rev-parse HEAD` -X main.buildTime=`date +'%Y-%m-%d_%T%Z'`" -o certchecker_linux main.go | ||
env: | ||
CGO_ENABLED: 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# This workflow will build a golang project | ||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go | ||
|
||
name: Release Cert Checker | ||
|
||
on: | ||
push: | ||
tags: | ||
- '*' | ||
|
||
jobs: | ||
|
||
build: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
|
||
- name: Set up Go | ||
uses: actions/setup-go@v5 | ||
with: | ||
go-version: '1.22' | ||
|
||
- name: Build | ||
run: go build -ldflags "-s -w -X main.sha1ver=`git rev-parse HEAD` -X main.buildTime=`date +'%Y-%m-%d_%T%Z'`" -o certchecker_linux main.go | ||
env: | ||
CGO_ENABLED: 0 | ||
|
||
- uses: ncipollo/release-action@v1 | ||
with: | ||
artifacts: "certchecker_linux" | ||
token: ${{ secrets.GITHUB_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
certs*.html | ||
tlsscan_* | ||
*.json | ||
Makefile |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
<html> | ||
<head> | ||
<style> | ||
table, th, td { | ||
border: 1px solid; | ||
} | ||
table { | ||
width: 100%; | ||
border-collapse: collapse; | ||
} | ||
.expired_true { | ||
background-color: red; | ||
} | ||
.verified_false { | ||
background-color: yellow; | ||
} | ||
.sni_true { | ||
background-color: green; | ||
} | ||
</style> | ||
</head> | ||
<h1>Certs</h1> | ||
<table> | ||
<tr> | ||
<th>HostIP</th> | ||
<th>HostDNS</th> | ||
<th>HostPort</th> | ||
<th>Hostname Verified</th> | ||
<th>SNI Verified</th> | ||
<th>Subject CN</th> | ||
<th>DNS Names</th> | ||
<th>IP Addresses</th> | ||
<th>Issuer</th> | ||
<th>Expiry</th> | ||
<th>Expired</th> | ||
</tr> | ||
|
||
{{range .TlsCerts }} | ||
<tr> | ||
<td>{{.HostIP}}</td> | ||
<td>{{.HostDNS}}</td> | ||
<td>{{.HostPort}}</td> | ||
<td class="verified_{{.HostNameVerified}}">{{.HostNameVerified}}</td> | ||
<td class="sni_{{.SNIVerified}}">{{.SNIVerified}}</td> | ||
<td>{{.SubjectCN}}</td> | ||
<td>{{.DNSNames}}</td> | ||
<td>{{.IPAddresses}}</td> | ||
<td>{{.Issuer}}</td> | ||
<td>{{.Expiry}}</td> | ||
<td class="expired_{{.Expired}}">{{.Expired}}</td> | ||
</tr> | ||
{{end}} | ||
</table> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
module github.com/saschamonteiro/certchecker | ||
|
||
go 1.22 | ||
|
||
require ( | ||
github.com/alexeyco/simpletable v1.0.0 | ||
golang.org/x/sync v0.7.0 | ||
) | ||
|
||
require ( | ||
github.com/mattn/go-runewidth v0.0.16 // indirect | ||
github.com/rivo/uniseg v0.4.7 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
github.com/alexeyco/simpletable v1.0.0 h1:ZQ+LvJ4bmoeHb+dclF64d0LX+7QAi7awsfCrptZrpHk= | ||
github.com/alexeyco/simpletable v1.0.0/go.mod h1:VJWVTtGUnW7EKbMRH8cE13SigKGx/1fO2SeeOiGeBkk= | ||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= | ||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= | ||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | ||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= | ||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= | ||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= | ||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package app | ||
|
||
import ( | ||
"context" | ||
"embed" | ||
"fmt" | ||
"net" | ||
"sort" | ||
"strings" | ||
|
||
"github.com/saschamonteiro/certchecker/internal/certs" | ||
"github.com/saschamonteiro/certchecker/internal/output" | ||
"golang.org/x/sync/errgroup" | ||
) | ||
|
||
func StartTlsCollect(cidrAddressList string, portList string, skipNoDnsFound bool, Assets embed.FS, htmlOut string, jsonOut string) { | ||
cidrAdd := strings.Split(cidrAddressList, ",") | ||
allHosts := []string{} | ||
for _, cidrAddress := range cidrAdd { | ||
hosts, _ := hostsFromCIDR(cidrAddress) | ||
allHosts = append(allHosts, hosts...) | ||
} | ||
ports := strings.Split(portList, ",") | ||
g, ctx := errgroup.WithContext(context.Background()) | ||
resultChan := make(chan []certs.TlsCert, len(allHosts)*len(ports)) | ||
result := make([]certs.TlsCert, 0) | ||
g.SetLimit(128) | ||
fmt.Printf("Scanning CIDRs:%v [ports:%s], please wait ", cidrAddressList, portList) | ||
for _, host := range allHosts { | ||
a := host | ||
g.Go(func() error { | ||
cres := findHostCerts(a, ports, skipNoDnsFound) | ||
select { | ||
case resultChan <- cres: | ||
case <-ctx.Done(): | ||
return context.Canceled | ||
default: | ||
} | ||
return nil | ||
}) | ||
} | ||
if err := g.Wait(); err != nil { | ||
fmt.Printf("ERROR: %+v\n", err) | ||
return | ||
} | ||
|
||
close(resultChan) | ||
for val := range resultChan { | ||
result = append(result, val...) | ||
} | ||
fmt.Printf("\nFound %v TLS Certs\n", len(result)) | ||
sort.Slice(result, func(i, j int) bool { return result[i].Expiry.Before(result[j].Expiry) }) | ||
|
||
output.ShowCertTable(result) | ||
|
||
if htmlOut != "" { | ||
output.CreateOutFile(result, htmlOut, "certs_html.tmpl", Assets) | ||
} | ||
if jsonOut != "" { | ||
output.CreateJsonFile(result, jsonOut) | ||
} | ||
} | ||
|
||
func findHostCerts(ip string, ports []string, skipNoDnsFound bool) []certs.TlsCert { | ||
serveraddr, err := net.LookupAddr(ip) | ||
cres := []certs.TlsCert{} | ||
if err == nil && len(serveraddr) > 0 { | ||
serverN := strings.TrimRight(serveraddr[0], ".") | ||
for _, port := range ports { | ||
c := certs.CheckCert(serverN, port, ip) | ||
if c.Issuer != "" { | ||
cres = append(cres, c) | ||
} | ||
} | ||
} else { | ||
if skipNoDnsFound { | ||
return nil | ||
} | ||
for _, port := range ports { | ||
c := certs.CheckCert(ip, port, ip) | ||
if c.Issuer != "" { | ||
cres = append(cres, c) | ||
} | ||
} | ||
} | ||
return cres | ||
} | ||
|
||
func hostsFromCIDR(cidr string) ([]string, error) { | ||
ip, ipnet, err := net.ParseCIDR(cidr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var ips []string | ||
for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) { | ||
ips = append(ips, ip.String()) | ||
} | ||
if len(ips) == 1 { | ||
return ips, nil | ||
} | ||
return ips[1 : len(ips)-1], nil | ||
} | ||
|
||
func inc(ip net.IP) { | ||
for j := len(ip) - 1; j >= 0; j-- { | ||
ip[j]++ | ||
if ip[j] > 0 { | ||
break | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package certs | ||
|
||
import ( | ||
"crypto/tls" | ||
"fmt" | ||
"net" | ||
"strings" | ||
"time" | ||
) | ||
|
||
type TlsCert struct { | ||
HostNameVerified bool `json:"hostnameVerified"` | ||
SubjectCN string `json:"subjectCN"` | ||
DNSNames string `json:"dnsNames"` | ||
IPAddresses string `json:"ipAddresses"` | ||
Issuer string `json:"issuer"` | ||
Expiry time.Time `json:"expiry"` | ||
Expired bool `json:"expired"` | ||
HostDNS string `json:"hostDNS"` | ||
HostIP string `json:"hostIP"` | ||
HostPort string `json:"hostPort"` | ||
SNIVerified bool `json:"sniVerified"` | ||
} | ||
type TlsPageData struct { | ||
TlsCerts []TlsCert | ||
} | ||
|
||
func CheckCert(server, port, ip string) TlsCert { | ||
hostnameVerified := false | ||
SNIVerified := false | ||
conf := &tls.Config{InsecureSkipVerify: false} | ||
if server != ip { | ||
conf.ServerName = server | ||
} | ||
// fmt.Printf("\n --> Start: %s\n", server) | ||
conn, err := tls.DialWithDialer(&net.Dialer{Timeout: 1 * time.Second}, "tcp", ip+":"+port, conf) | ||
if err != nil { | ||
// fmt.Printf("DialWithSNI Error %v\n", err) | ||
// fmt.Println("SecureTLS failed, Try InsecureSkipVerify", err) | ||
conn, err = tls.DialWithDialer(&net.Dialer{Timeout: 1 * time.Second}, "tcp", ip+":"+port, &tls.Config{InsecureSkipVerify: true}) | ||
if err != nil { | ||
if strings.Contains(err.Error(), "network is unreachable") || strings.Contains(err.Error(), "i/o timeout") || strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "no such host") || strings.Contains(err.Error(), "no route to host") { | ||
return TlsCert{} | ||
} else { | ||
fmt.Printf("Server doesn't support SSL certificate err: %v\n", err.Error()) | ||
} | ||
return TlsCert{} | ||
} else { | ||
// fmt.Println("trying hostname validation") | ||
if strings.Split(server, ":")[0] == conn.ConnectionState().PeerCertificates[0].Subject.CommonName { | ||
hostnameVerified = true | ||
} else { | ||
for _, dns := range conn.ConnectionState().PeerCertificates[0].DNSNames { | ||
if strings.Split(server, ":")[0] == dns { | ||
hostnameVerified = true | ||
} | ||
} | ||
} | ||
} | ||
} else { | ||
|
||
err = conn.VerifyHostname(strings.Split(server, ":")[0]) | ||
if err != nil { | ||
fmt.Printf("Hostname doesn't match with certificate: %v\n", err.Error()) | ||
} else { | ||
// fmt.Printf("SNIVerified", server, conn.ConnectionState().PeerCertificates[0].Subject.CommonName) | ||
hostnameVerified = true | ||
SNIVerified = true | ||
} | ||
|
||
} | ||
// fmt.Printf("ServerName: %v\n", conn.ConnectionState().) | ||
expiry := conn.ConnectionState().PeerCertificates[0].NotAfter | ||
expired := expiry.Before(time.Now()) | ||
// expiredString := string(colorGreen) + "false" + string(colorReset) | ||
// if expired { | ||
// expiredString = string(colorRed) + "true" + string(colorReset) | ||
// } | ||
// fmt.Printf("HostnameVerified: %v\nSubject CN: %v\nDNSNames: %v\nIPAddr: %v\nIssuer: %s\nExpiry: %v\nExpired: %v\n\n", hostnameVerified, conn.ConnectionState().PeerCertificates[0].Subject.CommonName, conn.ConnectionState().PeerCertificates[0].DNSNames, conn.ConnectionState().PeerCertificates[0].IPAddresses, conn.ConnectionState().PeerCertificates[0].Issuer, expiry.Format(time.RFC850), expiredString) | ||
// fmt.Printf("-- %+v\n", conn.ConnectionState().PeerCertificates[0]) | ||
cert := TlsCert{ | ||
HostNameVerified: hostnameVerified, | ||
SubjectCN: conn.ConnectionState().PeerCertificates[0].Subject.CommonName, | ||
DNSNames: fmt.Sprintf("%s", conn.ConnectionState().PeerCertificates[0].DNSNames), | ||
IPAddresses: fmt.Sprintf("%s", conn.ConnectionState().PeerCertificates[0].IPAddresses), | ||
Issuer: conn.ConnectionState().PeerCertificates[0].Issuer.String(), | ||
Expiry: expiry, | ||
Expired: expired, | ||
HostDNS: server, | ||
HostIP: ip, | ||
HostPort: port, | ||
SNIVerified: SNIVerified, | ||
} | ||
if ip == server { | ||
cert.HostDNS = "-" | ||
} | ||
fmt.Printf(".") | ||
return cert | ||
} |
Oops, something went wrong.