This repository has been archived by the owner on Nov 24, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmain.go
130 lines (117 loc) · 3.34 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package main
import (
"bufio"
"crypto/tls"
"encoding/json"
"flag"
"fmt"
"net"
"os"
"strings"
"sync"
"time"
)
type result struct {
Target string `json:"target"`
SANs []string `json:"SANs"`
}
var workers int
var jsonOutput bool
var tlsTimeout time.Duration
func init() {
flag.IntVar(&workers, "workers", 250, "Number of workers.")
flag.BoolVar(&jsonOutput, "json", false, "Output JSON.")
flag.DurationVar(&tlsTimeout, "timeout", 2500*time.Millisecond, "Connection timeout as duration, e.g. 2s or 800ms")
flag.Parse()
}
func main() {
printDisclaimer()
// Start Workers
scanQueue := make(chan func() result, 20)
resultsQueue := make(chan result, 20)
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
wg.Add(1)
go func(queue <-chan func() result, results chan<- result) {
for queuedTask := range queue {
results <- queuedTask()
}
wg.Done()
}(scanQueue, resultsQueue)
}
// Fill input queue with tasks
go func(scanQueue chan<- func() result) {
// Read from stdin
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
target := scanner.Text()
// Filter input
sanitized := sanitizeInput(target)
// This queues the scan
scanQueue <- getScanFunc(sanitized)
}
err := scanner.Err()
if err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf("Error on input processing: %s", err.Error()))
}
close(scanQueue)
}(scanQueue)
// Close results queue after all workers are finished
go func(resultsQueue chan result) {
wg.Wait()
close(resultsQueue)
}(resultsQueue)
// Process results
for result := range resultsQueue {
processOutput(jsonOutput, result)
}
}
func printDisclaimer() {
fmt.Fprintln(os.Stderr, "SANextract - fetch TLS certificates from endpoints and extract Subject Alternative Names")
fmt.Fprintln(os.Stderr, "By Michael Eder, HvS-Consulting AG")
fmt.Fprintln(os.Stderr, "https://www.hvs-consulting.de/ https://twitter.com/michael_eder_")
fmt.Fprintln(os.Stderr, "")
}
// This returns a closure that simply needs to be called by a worker to perform the check against a target
func getScanFunc(target string) func() result {
return func() result {
// Connect to the remote server
conn, err := net.DialTimeout("tcp", target, tlsTimeout)
if err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf("%s: Failed to connect: %s", target, err.Error()))
return result{}
}
defer conn.Close()
// For SNI, we need the host name. The input has already been sanitized, so we assume something like <host>:<port> here.
// This is a best effort approach
host := strings.Split(target, ":")[0]
// Do the TLS handshake
tlsConn := tls.Client(conn, &tls.Config{ServerName: host, InsecureSkipVerify: true}) // Skip the certificate verification as this is not of interest
err = tlsConn.Handshake()
if err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf("%s: Failed to connect: %s", target, err.Error()))
return result{}
}
cs := tlsConn.ConnectionState()
r := result{
Target: target,
SANs: []string{},
}
r.SANs = cs.PeerCertificates[0].DNSNames
return r
}
}
func processOutput(printJSON bool, r result) {
if r.Target == "" {
return
}
if printJSON {
marshalled, err := json.Marshal(r)
if err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf("Error marshalling result for %s", r.Target))
}
fmt.Println(string(marshalled))
} else {
fmt.Println(strings.Join(r.SANs, "\n"))
}
}