Skip to content

Commit

Permalink
Refactored the code to use workkers (10) to run multiple scans concur…
Browse files Browse the repository at this point in the history
…rently.
  • Loading branch information
vaughany committed May 16, 2023
1 parent e16d9c2 commit dddd1b0
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 62 deletions.
178 changes: 129 additions & 49 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"
"net/http/cookiejar"
"os"
"sort"
"strings"
"sync"
"time"
Expand All @@ -21,11 +22,19 @@ var urls = []string{

type Result int

type TLSResult struct {
type URLTestResult struct {
tls10, tls11, tls12, tls13 Result
tls10note, tls11note, tls12note, tls13note string
}

const (
resultUnknown Result = iota
resultSupportedOkay
resultSupportedNotOkay
resultUnsupportedOkay
resultUnsupportedNotOkay
)

var (
versionsStrings = map[uint16]string{
tls.VersionTLS10: "TLS 1.0",
Expand Down Expand Up @@ -58,9 +67,16 @@ var (
resultUnsupportedNotOkay: `❌`,
}

plural string
// Where we'll store all the results of the tests until we're ready to display it or write it out.
TestResults = make(map[string]URLTestResult)
TestResultsMutex = sync.RWMutex{}

jar *cookiejar.Jar
fh *os.File
err error

plural string
numJobs int
)

const (
Expand All @@ -77,15 +93,11 @@ const (
testNotATLSHandshake = `first record does not look like a TLS handshake`
testHTTPResponse = `server gave HTTP response to HTTPS client`

resultUnknown Result = iota
resultSupportedOkay
resultSupportedNotOkay
resultUnsupportedOkay
resultUnsupportedNotOkay
workers = 10
)

func main() {
fmt.Println("TLS Tester v0.2")
fmt.Println("TLS Tester v0.3")

// Flags.
url := flag.String(`url`, ``, `URL to test (e.g. 'google.com')`)
Expand All @@ -94,45 +106,72 @@ func main() {
urls = []string{*url}
}

// Put the number of URLs to be tested into a variable as we use it in several places for output and channel size.
numJobs = len(urls)

// What we're doing, and pluralising the output if needs be (I *hate* e.g. '1 URLs').
if len(urls) > 1 {
if numJobs > 1 {
plural = `s`
}
fmt.Printf("Checking %d URL%s for TLS security\n", len(urls), plural)
fmt.Printf("Checking %d URL%s for TLS security\n", numJobs, plural)

startupTime := time.Now()

// Open a file for writing.
fh, err := os.Create(`./output.csv`)
// Open a file for writing now and quit early if it fails.
fh, err = os.Create(`./output.csv`)
if err != nil {
panic(err)
}
defer fh.Close()

// Writing the 'header' of the CSV file.
csvLine := fmt.Sprintf("URL,%s,%s,%s,%s,%s note,%s note,%s note,%s note\n",
versionsStrings[tls.VersionTLS10], versionsStrings[tls.VersionTLS11], versionsStrings[tls.VersionTLS12], versionsStrings[tls.VersionTLS13],
versionsStrings[tls.VersionTLS10], versionsStrings[tls.VersionTLS11], versionsStrings[tls.VersionTLS12], versionsStrings[tls.VersionTLS13],
)
_, err = fh.WriteString(csvLine)
if err != nil {
panic(err)
}

// Set up a new cookie jar (some websites fail if a cookie cannot be written and then read back).
jar, err = cookiejar.New(nil)
if err != nil {
panic(err)
}

for i, url := range urls {
fmt.Printf("% 5d: %50s:\t\t", i+1, url)
// Set up the jobs and results channels.
jobs := make(chan string, numJobs)
results := make(chan string, numJobs)

// Spin up some workers.
for w := 1; w <= workers; w++ {
go processURL(w, jobs, results)
}

// Send the jobs.
for _, url := range urls {
jobs <- url
}
close(jobs)

// Receive the results.
for a := 1; a <= numJobs; a++ {
<-results
}

fmt.Printf("Processing complete.\n\n")

// Write out the data to screen, CSV, wherever.
var wg sync.WaitGroup

wg.Add(2)
writeScreen(TestResults, &wg)
writeCSV(TestResults, &wg)
wg.Wait()

fmt.Printf("Done. Tested %d URL%s in %s.\n", numJobs, plural, time.Since(startupTime).Round(time.Millisecond))
}

func processURL(id int, jobs <-chan string, results chan<- string) {
for url := range jobs {
fmt.Printf(" Processing %s...\n", url)

// Set up a waitgroup for the four TLS version tests.
var wg sync.WaitGroup

// Set defaults for the output.
testResult := TLSResult{resultUnknown, resultUnknown, resultUnknown, resultUnknown, ``, ``, ``, ``}
testResult := URLTestResult{resultUnknown, resultUnknown, resultUnknown, resultUnknown, ``, ``, ``, ``}

for _, tlsVersion := range []uint16{tls.VersionTLS10, tls.VersionTLS11, tls.VersionTLS12, tls.VersionTLS13} {
wg.Add(1)
Expand All @@ -142,33 +181,15 @@ func main() {
// Wait for all the tests to complete.
wg.Wait()

// Basic 'tick', 'cross' or 'unknown' output for the screen.
fmt.Printf("%s %s %s %s\n", resultsIcons[testResult.tls10], resultsIcons[testResult.tls11], resultsIcons[testResult.tls12], resultsIcons[testResult.tls13])

// Create one line of the CSV file from the results and notes of this URL's test.
csvLine := fmt.Sprintf("%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
url,
resultsShortStrings[testResult.tls10],
resultsShortStrings[testResult.tls11],
resultsShortStrings[testResult.tls12],
resultsShortStrings[testResult.tls13],
testResult.tls10note,
testResult.tls11note,
testResult.tls12note,
testResult.tls13note,
)
TestResultsMutex.Lock()
TestResults[url] = testResult
TestResultsMutex.Unlock()

// Write this URL's tests out to the CSV file.
_, err := fh.WriteString(csvLine)
if err != nil {
panic(err)
}
results <- url
}

fmt.Printf("Done. Tested %d URL%s in %s.\n", len(urls), plural, time.Since(startupTime).Round(time.Millisecond))
}

func testTLSVersion(tlsVersion uint16, url string, testResult *TLSResult, wg *sync.WaitGroup) {
func testTLSVersion(tlsVersion uint16, url string, testResult *URLTestResult, wg *sync.WaitGroup) {
defer wg.Done()

var (
Expand All @@ -178,7 +199,7 @@ func testTLSVersion(tlsVersion uint16, url string, testResult *TLSResult, wg *sy

client := &http.Client{
Jar: jar,
Timeout: 3 * time.Second,
Timeout: 15 * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
MinVersion: tlsVersion,
Expand Down Expand Up @@ -280,3 +301,62 @@ func testTLSVersion(tlsVersion uint16, url string, testResult *TLSResult, wg *sy
testResult.tls13note = tlsURLNote
}
}

// Basic 'tick', 'cross' or 'unknown' output for the screen.
func writeScreen(results map[string]URLTestResult, wg *sync.WaitGroup) {
defer wg.Done()

// Sort the data via the keys.
keys := make([]string, 0, len(results))
for key := range results {
keys = append(keys, key)
}
sort.Strings(keys)

for i, key := range keys {
fmt.Printf("% 5d: %50s:\t\t%s %s %s %s\n", i+1, key, resultsIcons[results[key].tls10], resultsIcons[results[key].tls11], resultsIcons[results[key].tls12], resultsIcons[results[key].tls13])
}
}

func writeCSV(results map[string]URLTestResult, wg *sync.WaitGroup) {
defer wg.Done()

// Writing the 'header' of the CSV file.
csvLine := fmt.Sprintf("URL,%s,%s,%s,%s,%s note,%s note,%s note,%s note\n",
versionsStrings[tls.VersionTLS10], versionsStrings[tls.VersionTLS11], versionsStrings[tls.VersionTLS12], versionsStrings[tls.VersionTLS13],
versionsStrings[tls.VersionTLS10], versionsStrings[tls.VersionTLS11], versionsStrings[tls.VersionTLS12], versionsStrings[tls.VersionTLS13],
)
_, err := fh.WriteString(csvLine)
if err != nil {
panic(err)
}

keys := make([]string, 0, len(results))
for key := range results {
keys = append(keys, key)
}
sort.Strings(keys)

// Create one line of the CSV file from the results and notes of this URL's test.
for _, key := range keys {
// fmt.Printf("% 5d: %50s:\t\t%s %s %s %s\n", i+1, key, resultsIcons[results[key].tls10], resultsIcons[results[key].tls11], resultsIcons[results[key].tls12], resultsIcons[results[key].tls13])

csvLine = fmt.Sprintf("%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
key,
resultsShortStrings[results[key].tls10],
resultsShortStrings[results[key].tls11],
resultsShortStrings[results[key].tls12],
resultsShortStrings[results[key].tls13],
results[key].tls10note,
results[key].tls11note,
results[key].tls12note,
results[key].tls13note,
)

// Write this URL's tests out to the CSV file.
_, err = fh.WriteString(csvLine)
if err != nil {
panic(err)
}
}
}
42 changes: 29 additions & 13 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ TLS Tester connects to a given URL with all four versions of TLS and logs the re
## Using a pre-built binary

1. [Download a binary](https://github.com/vaughany/tls-tester/releases) for Linux or Windows.
2. Run it with e.g. `./tls-tester` or `tls-tester.exe`
2. Make it executable
* Linux: `chmod +x tls-tester`
* Windows: right-click on it, Properties, Unblock (IIRC).
3. Run it with e.g. `./tls-tester` or `tls-tester.exe`

---

## Command-line Options

By default, the program will scan four common URLs. Run the command with `-url your-domain.com` to scan a URL of your choice.
By default, the program will scan four common URLs, purely for example. Run the command with `-url your-domain.com` to scan a URL of your choice.

Run the command with `-h` for help.

Expand All @@ -38,17 +41,29 @@ Typical on-screen output looks like this, with the URL being tested and the resu
Remember that ideally, we want to see TLS 1.2 or 1.3, and **do not want** to see TLS 1.0 or 1.1.

```bash
TLS Tester v0.1
TLS Tester v0.3
Checking 4 URLs for TLS security
1: apple.com: ✅ ✅ ✅ ✅
2: facebook.com: ❌ ❌ ✅ ✅
3: google.com: ❌ ❌ ✅ ✅
4: twitter.com: ✅ ✅ ✅ ✅
| | | |
| | | +- TLS 1.3
| | +---- TLS 1.2
| +------- TLS 1.1
+---------- TLS 1.0
Processing apple.com...
Processing facebook.com...
Processing google.com...
Processing twitter.com...
Processing complete.

1: apple.com: ✅ ✅ ✅ ✅
2: facebook.com: ❌ ❌ ✅ ✅
3: google.com: ❌ ❌ ✅ ✅
4: twitter.com: ✅ ✅ ✅ ✅
Done. Tested 4 URLs in 533ms.
```

The icons indicate:

```bash
| | | |
| | | +- TLS 1.3
| | +---- TLS 1.2
| +------- TLS 1.1
+---------- TLS 1.0
```

* If TLS 1.0 or 1.1 are in use, a ❌ is shown in that column.
Expand Down Expand Up @@ -99,9 +114,10 @@ This is a basic tool, reporting only basic results. The results are neither defi

* **2022-05-15, v0.1:** initial release. Scans four common URLs by default or use `-url` to scan a URL of your choice.
* **2022-05-15, v0.2:** used goroutines and waitgroups to run the four TLS version tests concurrently-per-URL, significantly reducing the testing time.
* **2022-05-16, v0.3:** refactored the code to use multiple (10) workers / queues, so the process can be completed much faster if scanning many URLs.

---

## To-Do

1. Refactor the code to use multiple workers / queues, so the process can be completed much faster if scanning many URLs.
1. Percentages of TLS version use

0 comments on commit dddd1b0

Please sign in to comment.