Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
saschamonteiroatea committed Jul 24, 2024
0 parents commit 17ca78a
Show file tree
Hide file tree
Showing 11 changed files with 501 additions and 0 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/go.yml
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
31 changes: 31 additions & 0 deletions .github/workflows/release.yml
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 }}
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
certs*.html
tlsscan_*
*.json
Makefile
Empty file added README.md
Empty file.
54 changes: 54 additions & 0 deletions certs_html.tmpl
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>
13 changes: 13 additions & 0 deletions go.mod
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
)
11 changes: 11 additions & 0 deletions go.sum
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=
112 changes: 112 additions & 0 deletions internal/app/app.go
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
}
}
}
99 changes: 99 additions & 0 deletions internal/certs/certs.go
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
}
Loading

0 comments on commit 17ca78a

Please sign in to comment.