Skip to content

Commit 9968094

Browse files
author
Don Johnson
committed
added tls check
1 parent 4432902 commit 9968094

File tree

1 file changed

+195
-0
lines changed

1 file changed

+195
-0
lines changed

tls_check.go

+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"crypto/tls"
6+
"errors"
7+
"flag"
8+
"fmt"
9+
"net"
10+
"os"
11+
"os/signal"
12+
"sync"
13+
"syscall"
14+
"time"
15+
)
16+
17+
// Config holds the application configuration
18+
type Config struct {
19+
target string
20+
timeout time.Duration
21+
verbose bool
22+
}
23+
24+
// TLSChecker handles TLS connection analysis
25+
type TLSChecker struct {
26+
config Config
27+
mu sync.RWMutex
28+
results map[uint16]bool
29+
errCount int
30+
tlsVersion uint16
31+
}
32+
33+
// NewTLSChecker creates a new instance of TLSChecker
34+
func NewTLSChecker(cfg Config) *TLSChecker {
35+
return &TLSChecker{
36+
config: cfg,
37+
results: make(map[uint16]bool),
38+
}
39+
}
40+
41+
// Run executes the TLS checking process
42+
func (tc *TLSChecker) Run(ctx context.Context) error {
43+
// Initial connection to get TLS version
44+
if err := tc.checkTLSVersion(ctx); err != nil {
45+
return fmt.Errorf("initial TLS check failed: %w", err)
46+
}
47+
48+
// Test cipher suites concurrently
49+
return tc.testCipherSuites(ctx)
50+
}
51+
52+
func (tc *TLSChecker) checkTLSVersion(ctx context.Context) error {
53+
conn, err := tc.connect(ctx, &tls.Config{
54+
InsecureSkipVerify: true,
55+
})
56+
if err != nil {
57+
return err
58+
}
59+
defer conn.Close()
60+
61+
tc.tlsVersion = conn.ConnectionState().Version
62+
return nil
63+
}
64+
65+
func (tc *TLSChecker) connect(ctx context.Context, cfg *tls.Config) (*tls.Conn, error) {
66+
dialer := &net.Dialer{
67+
Timeout: tc.config.timeout,
68+
KeepAlive: tc.config.timeout,
69+
}
70+
71+
conn, err := tls.DialWithDialer(dialer, "tcp", tc.config.target, cfg)
72+
if err != nil {
73+
var netErr net.Error
74+
if errors.As(err, &netErr) && netErr.Timeout() {
75+
return nil, fmt.Errorf("connection timeout: %w", err)
76+
}
77+
return nil, fmt.Errorf("connection failed: %w", err)
78+
}
79+
80+
return conn, nil
81+
}
82+
83+
func (tc *TLSChecker) testCipherSuites(ctx context.Context) error {
84+
var wg sync.WaitGroup
85+
semaphore := make(chan struct{}, 10) // Limit concurrent connections
86+
87+
for _, suite := range tls.CipherSuites() {
88+
select {
89+
case <-ctx.Done():
90+
return ctx.Err()
91+
case semaphore <- struct{}{}:
92+
}
93+
94+
wg.Add(1)
95+
go func(suite *tls.CipherSuite) {
96+
defer wg.Done()
97+
defer func() { <-semaphore }()
98+
99+
cfg := &tls.Config{
100+
InsecureSkipVerify: true,
101+
CipherSuites: []uint16{suite.ID},
102+
MinVersion: tc.tlsVersion,
103+
MaxVersion: tc.tlsVersion,
104+
}
105+
106+
conn, err := tc.connect(ctx, cfg)
107+
if err != nil {
108+
if tc.config.verbose {
109+
fmt.Printf("Failed testing %s: %v\n", suite.Name, err)
110+
}
111+
tc.recordResult(suite.ID, false)
112+
return
113+
}
114+
defer conn.Close()
115+
116+
tc.recordResult(suite.ID, true)
117+
}(suite)
118+
}
119+
120+
wg.Wait()
121+
return nil
122+
}
123+
124+
func (tc *TLSChecker) recordResult(suiteID uint16, supported bool) {
125+
tc.mu.Lock()
126+
defer tc.mu.Unlock()
127+
tc.results[suiteID] = supported
128+
if !supported {
129+
tc.errCount++
130+
}
131+
}
132+
133+
func (tc *TLSChecker) printResults() {
134+
fmt.Printf("\nTLS Connection Information for %s:\n", tc.config.target)
135+
fmt.Printf("TLS Version: %s\n\n", getTLSVersionString(tc.tlsVersion))
136+
137+
fmt.Println("Supported Cipher Suites:")
138+
for _, suite := range tls.CipherSuites() {
139+
supported := tc.results[suite.ID]
140+
if supported {
141+
fmt.Printf("✓ %s\n", suite.Name)
142+
} else if tc.config.verbose {
143+
fmt.Printf("✗ %s\n", suite.Name)
144+
}
145+
}
146+
147+
fmt.Printf("\nSummary: %d supported, %d unsupported cipher suites\n",
148+
len(tc.results)-tc.errCount, tc.errCount)
149+
}
150+
151+
func getTLSVersionString(version uint16) string {
152+
versions := map[uint16]string{
153+
tls.VersionTLS10: "TLS 1.0",
154+
tls.VersionTLS11: "TLS 1.1",
155+
tls.VersionTLS12: "TLS 1.2",
156+
tls.VersionTLS13: "TLS 1.3",
157+
}
158+
if v, ok := versions[version]; ok {
159+
return v
160+
}
161+
return fmt.Sprintf("Unknown (0x%04x)", version)
162+
}
163+
164+
func main() {
165+
cfg := Config{}
166+
flag.StringVar(&cfg.target, "url", "", "Target URL (e.g., example.com:443)")
167+
flag.DurationVar(&cfg.timeout, "timeout", 5*time.Second, "Connection timeout")
168+
flag.BoolVar(&cfg.verbose, "verbose", false, "Show detailed output including failures")
169+
flag.Parse()
170+
171+
if cfg.target == "" {
172+
flag.Usage()
173+
os.Exit(1)
174+
}
175+
176+
// Create context with cancellation
177+
ctx, cancel := context.WithCancel(context.Background())
178+
defer cancel()
179+
180+
// Handle OS signals
181+
sigChan := make(chan os.Signal, 1)
182+
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
183+
go func() {
184+
<-sigChan
185+
cancel()
186+
}()
187+
188+
checker := NewTLSChecker(cfg)
189+
if err := checker.Run(ctx); err != nil {
190+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
191+
os.Exit(1)
192+
}
193+
194+
checker.printResults()
195+
}

0 commit comments

Comments
 (0)