Skip to content

Commit

Permalink
Webserver.
Browse files Browse the repository at this point in the history
  • Loading branch information
vaughany committed May 17, 2023
1 parent dddd1b0 commit 8ea2a46
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 42 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
*.backup
*.csv
output.csv
*.txt
bin/
dist/
170 changes: 135 additions & 35 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ package main

import (
"crypto/tls"
"embed"
"errors"
"flag"
"fmt"
"html/template"
"io"
"net/http"
"net/http/cookiejar"
"os"
Expand Down Expand Up @@ -93,11 +97,15 @@ const (
testNotATLSHandshake = `first record does not look like a TLS handshake`
testHTTPResponse = `server gave HTTP response to HTTPS client`

workers = 10
workers = 10
webserverURL = "localhost:8080"
)

//go:embed "templates/results.tmpl"
var efs embed.FS

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

// Flags.
url := flag.String(`url`, ``, `URL to test (e.g. 'google.com')`)
Expand All @@ -124,6 +132,27 @@ func main() {
}
defer fh.Close()

var wg sync.WaitGroup

// Web server.
mux := http.NewServeMux()
mux.HandleFunc("/", rootHandler)
mux.HandleFunc("/csv", csvHandler)
mux.HandleFunc("/favicon.ico", notFoundHandler) // Prevents browsers hitting the API more than once.
wg.Add(1)
go func() {
fmt.Printf("Starting web server on http://%s\n", webserverURL)
err := http.ListenAndServe(webserverURL, mux)

if errors.Is(err, http.ErrServerClosed) {
fmt.Printf("server closed\n")
} else if err != nil {
fmt.Printf("error starting server: %s\n", err)
os.Exit(1)
}
wg.Done()
}()

// 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 {
Expand All @@ -139,6 +168,9 @@ func main() {
go processURL(w, jobs, results)
}

// Wait long enough for the web server startup message to appear.
time.Sleep(50 * time.Millisecond)

// Send the jobs.
for _, url := range urls {
jobs <- url
Expand All @@ -153,14 +185,12 @@ func main() {
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()
writeScreen(&wg)
writeCSV(&wg)

fmt.Printf("Done. Tested %d URL%s in %s.\n", numJobs, plural, time.Since(startupTime).Round(time.Millisecond))
fmt.Printf("\nDone. Tested %d URL%s in %s. Check the website on http://%s for details.\n", numJobs, plural, time.Since(startupTime).Round(time.Millisecond), webserverURL)
wg.Wait()
}

func processURL(id int, jobs <-chan string, results chan<- string) {
Expand Down Expand Up @@ -202,8 +232,9 @@ func testTLSVersion(tlsVersion uint16, url string, testResult *URLTestResult, wg
Timeout: 15 * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
MinVersion: tlsVersion,
MaxVersion: tlsVersion,
InsecureSkipVerify: false,
MinVersion: tlsVersion,
MaxVersion: tlsVersion,
},
},
}
Expand Down Expand Up @@ -303,45 +334,44 @@ func testTLSVersion(tlsVersion uint16, url string, testResult *URLTestResult, wg
}

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

// Sort the data via the keys.
keys := make([]string, 0, len(results))
for key := range results {
// Set the length of TestResultsKeys based on the number of URLs.
keys := make([]string, 0, numJobs)

// Sort the results' keys.
for key := range TestResults {
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])
fmt.Printf("% 5d: %50s:\t\t%s %s %s %s\n", i+1, key, resultsIcons[TestResults[key].tls10], resultsIcons[TestResults[key].tls11], resultsIcons[TestResults[key].tls12], resultsIcons[TestResults[key].tls13])
}
}

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

// 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],
// Create the 'header' of the CSV file.
csvData += fmt.Sprintf("#,URL,%[1]s,%[2]s,%[3]s,%[4]s,%[1]s note,%[2]s note,%[3]s note,%[4]s note\n",
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 {
// Set the length of TestResultsKeys based on the number of URLs.
keys := make([]string, 0, numJobs)

// Sort the results' keys.
for key := range TestResults {
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",
// Iterate over the data to create a line of comma-separated data.
for i, key := range keys {
csvData += fmt.Sprintf("%d,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
i+1,
key,
resultsShortStrings[results[key].tls10],
resultsShortStrings[results[key].tls11],
Expand All @@ -352,11 +382,81 @@ func writeCSV(results map[string]URLTestResult, wg *sync.WaitGroup) {
results[key].tls12note,
results[key].tls13note,
)
}

return csvData
}

func writeCSV(wg *sync.WaitGroup) {
defer wg.Done()

csvData := createCSVString(TestResults)

// Write out the CSV file.
_, err = fh.WriteString(csvData)
if err != nil {
panic(err)
}
}

func csvHandler(w http.ResponseWriter, r *http.Request) {
csvData := createCSVString(TestResults)

// Start doing HTML output things.
w.WriteHeader(http.StatusTeapot) // Force a status.
w.Header().Set("Content-Type", "text/plain") // Force a content-type.

io.WriteString(w, csvData)
}

func rootHandler(w http.ResponseWriter, r *http.Request) {
type HTML struct {
ID int
URL string
TLS10, TLS11, TLS12, TLS13 string
TLS10Note, TLS11Note, TLS12Note, TLS13Note string
}

// Variable to hold all the output.
htmlOutput := []HTML{}

// Set the length of TestResultsKeys based on the number of URLs.
keys := make([]string, 0, numJobs)

// Sort the results' keys.
for key := range TestResults {
keys = append(keys, key)
}
sort.Strings(keys)

// Write this URL's tests out to the CSV file.
_, err = fh.WriteString(csvLine)
if err != nil {
panic(err)
for i, key := range keys {
line := HTML{
i + 1,
key,
resultsShortStrings[TestResults[key].tls10],
resultsShortStrings[TestResults[key].tls11],
resultsShortStrings[TestResults[key].tls12],
resultsShortStrings[TestResults[key].tls13],
TestResults[key].tls10note,
TestResults[key].tls11note,
TestResults[key].tls12note,
TestResults[key].tls13note,
}

htmlOutput = append(htmlOutput, line)
}

// Start doing HTML output things.
w.WriteHeader(http.StatusTeapot) // Force a status.
w.Header().Set("Content-Type", "text/html") // Force a content-type.

tmpl := template.Must(template.ParseFS(efs, "templates/*.tmpl"))
err = tmpl.Execute(w, htmlOutput)
if err != nil {
panic(err)
}
}

func notFoundHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
}
31 changes: 25 additions & 6 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,21 @@ 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.3
TLS Tester v0.4
Checking 4 URLs for TLS security
Starting web server on http://localhost:8080
Processing apple.com...
Processing facebook.com...
Processing google.com...
Processing twitter.com...
Processing google.com...
Processing facebook.com...
Processing complete.

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

Done. Tested 4 URLs in 688ms. Check the website on http://localhost:8080 for details.
```

The icons indicate:
Expand Down Expand Up @@ -101,12 +103,26 @@ twitter.com,okay,okay,okay,okay,protocol version not supported,protocol version

---

## Web Output

When run, the program will attempt to start a very basic web server on http://localhost:8080 (the on-screen output will mention this both at startup and at the end of the scan). It shows the URL, the results of the tests for all four TLS versions, and a complimentary link to [Qualys' SSL Labs](https://www.ssllabs.com/) with the URL pre-filled, should you want to test your URLs further.

If you test a large number of URLs, they should appear on the web page as soon as that URL has been fully tested. Simply hit refresh on your browser to update it.

The page is formatted using minimal [Bootstrap](https://getbootstrap.com) markup, and pulls the required CSS and JS from a CDN. The page will look much more basic without it.

The CSV data is also available to copy and paste elsewhere, via the web: http://localhost:8080/csv.

Press `Ctrl-C` to quit.

---

## Double checking

This is a basic tool, reporting only basic results. The results are neither definitive or conclusive, and errors especially may be the results of timeouts due to slow-running Internet between you and the target, as well as more mundane things. Looking deeper into your security is highly recommended.

1. If you're on Linux or MacOS, I suggest downloading `testssl.sh` from [testssl.sh](https://testssl.sh/) and testing your domains against it.
2. Alternatively, use SSL Labs' [SSl Test](https://www.ssllabs.com/ssltest/) (but remember to tick the 'keep results private' box).
2. Alternatively, use [Qualys' SSL Labs SSL Tester](https://www.ssllabs.com/ssltest/) (but remember to tick the 'keep results private' box).

---

Expand All @@ -115,9 +131,12 @@ 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.
* **2022-05-17, v0.4:** Now with a web server (http://localhost:8080) for better presentation of the results.

---

## To-Do

1. Percentages of TLS version use
1. Percentages of TLS version use.
2. Use e.g. `input.csv` file in order to specify multiple URLs to test.
3. Processing time on the web page.
42 changes: 42 additions & 0 deletions templates/results.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>TLS Tester</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
<body>
<div class="container">
<h1>TLS Tester</h1>

<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">URL</th>
<th scope="col">TLS 1.0</th>
<th scope="col">TLS 1.1</th>
<th scope="col">TLS 1.2</th>
<th scope="col">TLS 1.3</th>
<th scope="col">SSL Labs</th>
</tr>
</thead>
<tbody>
{{range .}}
<tr>
<th scope="col">{{.ID}}</th>
<td><a href="https://{{.URL}}">{{.URL}}</a></td>
<td>{{.TLS10}}</td>
<td>{{.TLS11}}</td>
<td>{{.TLS12}}</td>
<td>{{.TLS13}}</td>
<td><a href="https://www.ssllabs.com/ssltest/analyze.html?d={{.URL}}&hideResults=on" target="_blank">Test {{.URL}} at SSL Labs</a></td>
</tr>
{{end}}
</tbody>
</table>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>
</body>
</html>

0 comments on commit 8ea2a46

Please sign in to comment.