Learn how to test certificate chains with a Go program
Golang to the rescue: Saving DevOps from TLS turmoil
Find out about a use case that created a need for testing certificate chains, appropriate web server security settings, and the Go code used for testing.
If you ever need to validate certificates or certificate chains before deploying them, Golang provides a near foolproof test method. In this article, I will explain our use case that brought about the need for testing certificate chains, review appropriate web server security settings, and break down the Go code used for testing.
Recently I took on a project at work that has turned into quite the adventure. It's a small application that seems to cost a lot more money than it should. A third-party constructed this application, which is now maintained in-house. This project lives in Amazon Web Services and makes use of Elastic Load Balancers (ELBs).
Before I could do any re-engineering work, I had to resolve a critical issue—the certificates on the ELBs were about to expire and needed updating. The new certificates came from a large SSL vendor, which provided outdated as well as current documentation on their website. The documentation was also more suited to closed-source operating systems and applications. I mention this because if you are going to serve content over TLS, you better have an A rating on the Qualys SSL Test. To get that A rating, you must have the intermediate certificate as part of your chain.
There are many ways to test certificate chains, but testing can be a little confusing. I assumed that if the ELB, NGINX, or httpd started, it was a good sign. This was a false assumption on my part and I ended up serving a bad chain for a few minutes. This did not break the site, but it was definitely not the way I wanted things to remain.
I needed a tool that would fail if the certificate chain provided was incorrect. I wanted a lightweight tool that could be publicly accessible. Conducting a third-party analysis of the certificates and configuration was a requirement. There were no tools that I could find meeting this need, so I decided to build my own. I turned to the open source language, Golang.
Golang (or Go) is an open source language created at Google by Robert Griesemer, Rob Pike, and Ken Thompson. Go is a compiled, statically typed language in the tradition of Algol and C, with garbage collection, limited structural typing, memory safety features, and CSP-style concurrent programming features added. Go provides a fantastic balance between speed, functionality, and simplicity.
My Go web server makes use of three Go packages: log, crypto/tls, and net/http. You can download the code from GitHub.
Figure 1: The author's short Go program, available in GitHub.
The Go log package is pretty self explanatory—it's the package that enables logging. In this use case, there should be a spectacular failure at the sign of trouble. log has three helper functions: print, fatal, and panic. Output from the package goes to stderr. I used a fatal error to get the web server to stop and log any issue.
The Go crypto/tls package partially implements TLS 1.2, as specified in RFC 5246. This is the package that configures SSL/TLS versions that can be used, and it identifies preferred cipher suites and elliptic curves used during handshakes. This is the package that handles connections securely.
The critical package used in the Go web server is the net/http package. It is the Go implementation of HTTP. net/http has a function called ListenAndServeTLS. This provides the desired certificate checking functionality. According to the package's documentation for ListenAndServeTLS, "If the certificate is signed by a certificate authority, the certFile should be the concatenation of the server's certificate, any intermediates, and the CA's certificate."
The code creates a mux, short for HTTP request multiplexer. This mux has a function that creates an HTTP server with headers and content (Hello World!). cfg brings in all the TLS bits and pieces you see in a solid NGINX or httpd configuration. srv brings the pieces together and defines what port to listen on. The line log.Fatal(srv.ListenAndServeTLS("tls.crt", "tls.key")) defines the certificate files to use and logs a fatal error if they are not valid.
The Go code does exactly what I need it to do and nothing more, in only 34 lines of code. Once the code is compiled, the web server is less than 6MB. I was able to cobble together most of the Go code from other open source projects and articles (like this one). The web server can be safely deployed to any public server and have tests run against it for extra vetting.
Do you have a handy Go trick to share? Send your story ideas to firstname.lastname@example.org.