[Tool] TLS checker – Part I

Many of you will know about Qualys SSL Labs and their comprehensive (and free!) SSL Test. If you didn’t know go and check it now, it’s amazing. It does run a series of security checks on a given SSL endpoint, including protocol version and ciphersuite compatibility.

Alright, now that we’re all on the same page I wanted to share with you a little project I did for educational purposes. The project’s name is TLS Checker and it’s a little online tool that checks which TLS protocol version and ciphersuite combination does a given site support. It does not check anything else and there’s where any comparison with SSL Test from Qualys ends.

The purpose of this article is to show you how I built it. I had to learn a thing or two, like how to do some stuff with JavaScript for the frontend and Go for the backend. I hope this helps you too learning a thing or two…

First let’s have a look at it.

Go to https://pedroperezmsft.github.io/tlscheckerfront/ and give it a whirl.

tlschecker

How was it? Hope it didn’t fail for you, but if it did feel free to get in touch or just leave a comment here. Any other feedback is also welcome 🙂

Structure

TLS checker is made of two differentiated parts: Frontend and backend. The frontend is your typical HTML/CSS/JS combination uploaded to Github here. The backend is a service written in Go and running on Azure. In this article we’ll check in more detail how the frontend has been built.

I’ll publish a second part in due time about how the backend was built. Please note that in both parts this is an exercise for my own learning so it definitely has areas for improvement. You shouldn’t be following these articles for any production-level deployment.

Feel free to send your feedback 🙂

Frontend

The frontend could be itself divided in two parts:

  1. A form with 2 text fields, one checkbox and a submit button.
  2. A table with 3 columns and a text field for filtering the table.

The form asks you for 3 inputs:

  1. Host or FQDN: hostname or fully qualified domain name of the endpoint you would like to test, e.g.: bing.com
  2. Port number: This is the port where the endpoint is listening on. For the vast majority of HTTP sites this would be 443. This is TCP only.
  3. “Only supported combinations” checkbox: This option will pre-filter the result to just show combinations of TLS protocol version and ciphersuite where we could get a positive response from the endpoint.

The interesting part here is the action we take when sending the form, the rest is just simple HTML/CSS (as I rely on Bootstrap to make things prettier).

This is the part of the form where we define what’s the action to be taken when the form is submitted by the user:

<form action="javascript:void(0);" onsubmit="callTlsChecker()">

When we click on the “Check!” button, the action is to run a JavaScript function named callTlsChecker(). Let’s have a look at its code:

function callTlsChecker() {
  var host = document.getElementById("host").value;
  var port = document.getElementById("port").value;
  var url = 'https://tlschecker.azurewebsites.net/' + host + '/' + port;
// Clean up table first
for(var i = document.getElementById("resultsTable").rows.length; i > 1;i--)
{
document.getElementById("resultsTable").deleteRow(i -1);
}
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.send();
xhr.onload = function() {
 var text = xhr.responseText;
 var results = JSON.parse(text);
 results.Result.forEach(element => {
 if (document.getElementById('onlysupported').checked) {
 if (element.Supported == true) {
 addRow(element.Protocol, element.CipherSuite, element.Supported);
 };
 } else {
 addRow(element.Protocol, element.CipherSuite, element.Supported);
 };
 });
 };
 }

We start by getting the values from the form:

var host = document.getElementById("host").value;
var port = document.getElementById("port").value;

And building the URL we will query:

var url = 'https://tlschecker.azurewebsites.net/' + host + '/' + port;

This URL has to be in the format above, which would look like this with the default settings:

https://tlschecker.azurewebsites.net/bing.com/443

Immediately after I clean up the table to make sure we’re starting from scratch. In earlier versions of the site, when I ran a second test the results would be added after the ones from the first test, confusing me for a good while.

The code fetches the number of rows in an element whose ID is “resultsTable”, then deletes each row except the first one. Question #1 for the reader: Why I don’t delete all rows?

 

// Clean up table first
   for(var i = document.getElementById("resultsTable").rows.length; i > 1;i--)
   {
     document.getElementById("resultsTable").deleteRow(i -1);
   }

The answer to Question #1 is because the first row contains the headers of the table. I don’t want to delete those!

Now that we have the URL we’d like to query and the table is ready, we are ready to start querying for the data and working with it! In order to get the data we’re going to use an XMLHttpRequest object. This object would be responsible for sending a request to the backend and fetching the response.

We start by creating an instance of an XMLHttpRequest object. We have decided to call it xhr. Immediately after we are going to call two methods from the XMLHttpRequest object.

var xhr = new XMLHttpRequest();

The first method is open(), which initializes a new request. In our case, we have chosen to initialize the request by passing a “GET” HTTP verb and the value of url we have built before, which is https://tlschecker.azurewebsites.net – There is a third parameter I’ve passed to initialize the request. Question #2 for the reader: What is this parameter and why do you think we would choose to pass true here?

xhr.open("GET", url, true);

We have now an initialized request, which means it is ready to be sent. Let’s send it by using the send() method:

xhr.send();

The answer to Question #2 is: This parameter chooses if the request is asynchronous or not. If it’s not asynchronous our code execution will stall after xhr.send() until we get a full response from the server. In most cases that negatively impacts the customer experience. In our case there’s not a lot of difference as we can’t do anything else until we have a response anyway, but it’s good practice to have this asynchronous from the start, so in the future we don’t have to re-architect.

We have now sent a request to the remote server and are waiting for a response, but what does “we are waiting for a response” exactly mean? Or in other words, how do we know we have a response and can go ahead with the rest of our code?

On a synchronous method our code would be just waiting on xhr.send() to return something, but this is not the case. We have sent the request and moved on executing the rest of the code. As TLS Checker does not have any other features, the rest of the code is pretty much just what needs to be done when the data is back from the backend. How do we get that data and how can we be sure it’s all the data the backend server had to send back?

We do it by calling another method of XMLHttpRequest: onload(). This method only executes when we have received the complete response from the backend. We will call a function that parses and presents the results on-screen only when onload() happens.

In our specific case instead of calling a separate function we are building one inline:

xhr.onload = function() {

Once inside the function we need to get the response’s body into a variable. We know the backend returns a response in json format and luckily for us JavaScript can parse and convert json into an object with a single line:

var text = xhr.responseText;
 var results = JSON.parse(text);

 

Now we have an object called results that follows the schema of the json returned by the backend. Now it’s time to show the results to the user in a nice formatted table. We have a table already, so the logic here would be to go through each result which has 3 elements (Protocol, CipherSuite and Supported) and print them as columns in the table. One result per row.

But hold on, because we also had a “only supported combinations” checkbox in the HTML form, right? So we will first do a quick check if that was enabled. If it was enabled we will check for the value of the Supported column on each element, only printing those whose value is True. This way we make sure we only print those results that are supported, as requested by the user.

results.Result.forEach(element => {
      if (document.getElementById('onlysupported').checked) {
        if (element.Supported == true) {
          addRow(element.Protocol, element.CipherSuite, element.Supported);
        };

This is how we defined the checkbox. Please pay special attention to how we assigned an Id with value onlysupported to the checkbox object so we can find it later from our JavaScript above:

<input type="checkbox" id="onlysupported"> Only supported combinations

If the checbox is not checked, we will skip any further checks and just print the result. This way we make sure we print all results, regardless if they are supported or not.

} else {
         addRow(element.Protocol, element.CipherSuite, element.Supported);       };     });   };

Now there’s one last thing we have reviewed, which is the addRow() method we call to add a new row to the table. This is not part of a library, so this is a method we have to build ourselves too. Here’s the code:

function addRow(protocol,ciphersuite,supported)
  {   if (!document.getElementsByTagName) return;   tabBody=document.getElementsByTagName("tbody").item(0);   row=document.createElement("tr");   cell1 = document.createElement("td");   cell2 = document.createElement("td");   cell3 = document.createElement("td");   textnode1=document.createTextNode(protocol);   textnode2=document.createTextNode(ciphersuite);   textnode3=document.createTextNode(supported);   cell1.appendChild(textnode1);   cell2.appendChild(textnode2);   cell3.appendChild(textnode3);   if (supported == false) {cell3.style.color='red';} else {cell3.style.color='green';}   row.appendChild(cell1);   row.appendChild(cell2);   row.appendChild(cell3);   tabBody.appendChild(row);  }

The addRow() method takes three parameters, which are protocol, ciphersuite and supported. As you have probably imagined by now, these are the values of those columns for the row we’re going to add. Let’s have a look at the table HTML definition:

<table id="resultsTable" class="table table-dark">
<thead>
<tr>
<th scope="col">Protocol</th>
<th scope="col">Ciphersuite</th>
<th scope="col">Supported</th>
</tr>
</thead>
</table>

As you can see, we have just defined a table with 3 columns (defined as headers) and an empty body. As we have to fill up this body with rows, we need to locate it from our JavaScript code:

taBody=document.getElementsByTagName("tbody").item(0);

Note: I am just finding the table body by looking at the first element found of type tbody. That’s not great practice as if I were to add another table before this one the application would break. Ideally you should be finding the table body by looking at an Id as we have done before for other elements.

Next steps would be to create a row, create the three cells of the row and add the data to them:

row=document.createElement("tr");
  cell1 = document.createElement("td");
  cell2 = document.createElement("td");
  cell3 = document.createElement("td");
  textnode1=document.createTextNode(protocol);
  textnode2=document.createTextNode(ciphersuite);
  textnode3=document.createTextNode(supported);
  cell1.appendChild(textnode1);
  cell2.appendChild(textnode2);
  cell3.appendChild(textnode3);

Before printing results on screen let’s add a bit of colour. I have decided I would print the values of the supported column either in red if not supported or in green if supported. This is a simple attribute change we can do with JavaScript. The decision would be guided by a simple if statement. Cell3 is, as seen above, the one containing the supported value, so let’s make the change there:

if (supported == false) {cell3.style.color='red';} else {cell3.style.color='green';}

Now that we have the data where and how we want it, let’s just print it:

row.appendChild(cell1);
  row.appendChild(cell2);
  row.appendChild(cell3);
  tabBody.appendChild(row);
 }

And that’s it. Now we have a table full of results. These results are gathered from the backend in json format and printed as a table.

This will build you a fully functioning site like mine, but afterwards I added a little gimmick that has shown to be quite useful for users.

<input type="text" id="myInput" onkeyup="filterTable()" placeholder="Filter..">

This is the HTML definition of the text field that lets you filter the table for specific keywords. You just have to type in the keyword and JavaScript will filter the results (per row) in real time to those that match the text field’s input into any column’s value.

We call a function named filterTable() every time a key has been released (onkeyup). As the key has to have been pressed before (i.e. typed into the text field), the impression is that you’re filtering results as you write.

The function’s logic basically gets the value of the text field input and searches through the results table for it. If a row does not contain that value (even as part of its own values, e.g. “3D” for “3DES”) that row is hidden by changing its visibility attribute. If there’s a match, the row is left untouched.

function filterTable() {
  // Declare variables 
  var input, filter, table, tr, td, i;
  input = document.getElementById("myInput");
  filter = input.value.toUpperCase();
  table = document.getElementById("resultsTable");
  tr = table.getElementsByTagName("tr");

  // Loop through all table rows, and hide those who don't match the search query
  for (i = 0; i &lt; tr.length; i++) {
    td = tr[i].getElementsByTagName(&quot;td&quot;);
    for (cell = 0; cell <td> -1) {
          tr[i].style.display = "";
          break;
        } else {
         tr[i].style.display = "none";
       }
      }
    } 
  }
 }

We start by fetching the value from the text field input as follows:

input = document.getElementById("myInput");

Then we convert it to upper case. Question #3 for the reader: Why would we convert customer’s input to upper case?

filter = input.value.toUpperCase();

Next step is to find the table by looking at its Id and then get an array of the table’s rows, so we can iterate through them:

table = document.getElementById("resultsTable");
  tr = table.getElementsByTagName("tr");

Now here comes the interesting part. Let me define it in English first:

  1. We iterate through each row
  2. Then we iterate through each cell of each row
  3. Then for each cell of each row we check if filter is part of its value.
    1. Note: We convert the cell’s value to upper case too!
  4. value.IndexOf(string) returns -1 if string can’t be found as part of value.
    1. So if the result is bigger than -1, means we found the string so we should leave this row untouched and stop checking other cells in this row.
    2. If it returns -1, we change the visibility of the row to none, which effectively hides the row.
// Loop through all table rows, and hide those who don't match the search query
  for (i = 0; i &lt; tr.length; i++) {
    td = tr[i].getElementsByTagName(&quot;td&quot;);
    for (cell = 0; cell <td> -1) {
          tr[i].style.display = "";
          break;
        } else {
         tr[i].style.display = "none";
       }
      }
    } 
  }

The answer to Question #3 is: The decision to change customer’s input to upper case can’t be answered before seeing how we also change the cell’s values to upper case while making comparisons.

if (td[cell].innerHTML.toUpperCase().indexOf(filter) > -1) {

This makes our filter case insensitive, meaning that if the customer searches for 3DeS they will still match 3DES. This makes sense in our context as there’s no case sensitivity in the name of the ciphers or any other value we present to the customer.

The choice of upper case instead of lower case is arbitrary. You should be good to go as long as you choose the same casing in both filter and the comparison.

Summary

In this article we have learned to code a website with HTML, CSS and JavaScript that pulls data from a json source, formats it into a table and presents it to customer. We have also seen how to filter that table in real time.

Any questions, comments or concerns please leave your message below or get in touch in any other way.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.